From 731a652cae86bc4384bed67a224dc1b4be6f22c0 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Apr 2026 19:35:59 +0300 Subject: [PATCH 01/94] feat(tests): add platform verification script for skill snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs each test case through the full pipeline: cf-init → stubs → preRun → skill script → cf-edit → db-create → LoadConfigFromFiles → UpdateDBCfg. Handles typed-input, args-only, standalone (SKD/MXL), and EPF skills. Results: 118/145 pass, findings documented in debug/snapshot-verify/. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/skills/verify-snapshots.mjs | 720 ++++++++++++++++++++++++++++++ 1 file changed, 720 insertions(+) create mode 100644 tests/skills/verify-snapshots.mjs diff --git a/tests/skills/verify-snapshots.mjs b/tests/skills/verify-snapshots.mjs new file mode 100644 index 00000000..73dce95e --- /dev/null +++ b/tests/skills/verify-snapshots.mjs @@ -0,0 +1,720 @@ +#!/usr/bin/env node +// verify-snapshots v0.2 — Platform verification of skill test snapshots +// Reruns skill scripts from test-case DSL, then loads into 1C platform. +// Usage: node tests/skills/verify-snapshots.mjs [--skill meta-compile] [--case catalog-basic] [--runtime powershell|python] [--keep] [--verbose] +// Supports: meta-compile, form-compile, form-add, form-edit, skd-compile, skd-edit, +// role-compile, subsystem-compile, subsystem-edit, mxl-compile, template-add, +// help-add, cf-init, cf-edit, epf-init, epf-add-form, meta-edit, interface-edit, +// cfe-init, cfe-borrow, cfe-patch-method + +import { execFileSync } from 'child_process'; +import { existsSync, mkdirSync, mkdtempSync, rmSync, readFileSync, writeFileSync, + readdirSync, statSync, cpSync } from 'fs'; +import { join, resolve, dirname, basename } from 'path'; +import { tmpdir } from 'os'; + +// ─── Paths ────────────────────────────────────────────────────────────────── + +const ROOT = resolve(dirname(new URL(import.meta.url).pathname).replace(/^\/([A-Z]:)/i, '$1')); +const REPO_ROOT = resolve(ROOT, '../..'); +const SKILLS = resolve(REPO_ROOT, '.claude/skills'); +const CASES = resolve(ROOT, 'cases'); +const REPORT_DIR = resolve(REPO_ROOT, 'debug/snapshot-verify'); + +// ─── CLI args ─────────────────────────────────────────────────────────────── + +function parseArgs(argv) { + const args = { skill: null, caseName: null, runtime: 'powershell', keep: false, verbose: false }; + const rest = argv.slice(2); + for (let i = 0; i < rest.length; i++) { + const a = rest[i]; + if (a === '--skill' && rest[i + 1]) { args.skill = rest[++i]; continue; } + if (a === '--case' && rest[i + 1]) { args.caseName = rest[++i]; continue; } + if (a === '--runtime' && rest[i + 1]) { args.runtime = rest[++i]; continue; } + if (a === '--keep') { args.keep = true; continue; } + if (a === '--verbose' || a === '-v') { args.verbose = true; continue; } + } + return args; +} + +// ─── Platform context ─────────────────────────────────────────────────────── + +function loadV8Context() { + const projectFile = join(REPO_ROOT, '.v8-project.json'); + if (!existsSync(projectFile)) return null; + try { + const proj = JSON.parse(readFileSync(projectFile, 'utf8')); + const v8bin = proj.v8path; + const v8exe = v8bin ? (existsSync(join(v8bin, '1cv8.exe')) ? join(v8bin, '1cv8.exe') : null) : null; + if (!v8exe) return null; + return { v8path: v8bin, v8exe }; + } catch { return null; } +} + +// ─── Script execution ─────────────────────────────────────────────────────── + +function resolveScript(relPath, runtime) { + const ext = runtime === 'python' ? '.py' : '.ps1'; + const full = join(SKILLS, relPath + ext); + if (!existsSync(full)) throw new Error(`Script not found: ${full}`); + return full; +} + +function execSkill(runtime, scriptRelPath, args, timeout = 60_000) { + const scriptPath = resolveScript(scriptRelPath, runtime); + if (runtime === 'python') { + return execFileSync(process.env.PYTHON || 'python', [scriptPath, ...args], { + encoding: 'utf8', timeout, stdio: ['pipe', 'pipe', 'pipe'], cwd: REPO_ROOT, + }); + } + return execFileSync('powershell.exe', [ + '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', + '-File', scriptPath, ...args + ], { encoding: 'utf8', timeout, stdio: ['pipe', 'pipe', 'pipe'], cwd: REPO_ROOT }); +} + +// ─── Dependency resolution ────────────────────────────────────────────────── + +const ID = '[\\w\\u0400-\\u04FF]+'; + +function extractTypeRefs(input) { + const refs = new Map(); + const json = JSON.stringify(input); + + const refPattern = new RegExp(`(Catalog|Document|Enum|ChartOfAccounts|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|ExchangePlan)Ref\\.(${ID})`, 'g'); + let m; + while ((m = refPattern.exec(json)) !== null) { + refs.set(`${m[1]}.${m[2]}`, { type: m[1], name: m[2] }); + } + + const directPattern = new RegExp(`(ChartOfAccounts|ChartOfCalculationTypes|ChartOfCharacteristicTypes)\\.(${ID})`, 'g'); + while ((m = directPattern.exec(json)) !== null) { + refs.set(`${m[1]}.${m[2]}`, { type: m[1], name: m[2] }); + } + + const objPattern = new RegExp(`(Document|Catalog|BusinessProcess|Task|ExchangePlan)Object\\.(${ID})`, 'g'); + while ((m = objPattern.exec(json)) !== null) { + refs.set(`${m[1]}.${m[2]}`, { type: m[1], name: m[2] }); + } + + const modPattern = new RegExp(`CommonModule\\.(${ID})\\.${ID}`, 'g'); + while ((m = modPattern.exec(json)) !== null) { + refs.set(`CommonModule.${m[1]}`, { type: 'CommonModule', name: m[1] }); + } + + if (input && input.type === 'ScheduledJob' && input.methodName) { + const parts = input.methodName.split('.'); + if (parts.length >= 2) { + refs.set(`CommonModule.${parts[0]}`, { type: 'CommonModule', name: parts[0] }); + } + } + + return refs; +} + +// ─── Structural dependencies ──────────────────────────────────────────────── + +function getStructuralDeps(input) { + const deps = []; + const inputs = Array.isArray(input) ? input : [input]; + if (!inputs[0] || !inputs[0].type) return deps; + + for (const inp of inputs) { + const regTypePrefix = { + AccumulationRegister: 'AccumulationRegister', + AccountingRegister: 'AccountingRegister', + CalculationRegister: 'CalculationRegister', + }[inp.type]; + + if (regTypePrefix) { + deps.push({ + type: 'Document', name: 'ТестовыйДокумент', + dsl: { type: 'Document', name: 'ТестовыйДокумент' }, + postEdit: [{ op: 'add-registerRecord', val: `${regTypePrefix}.${inp.name}` }], + }); + } + + switch (inp.type) { + case 'BusinessProcess': { + const taskRef = inp.task; + if (taskRef) { + const taskName = taskRef.split('.').pop(); + deps.push({ type: 'Task', name: taskName, dsl: { type: 'Task', name: taskName, descriptionLength: 100 } }); + } + break; + } + case 'DocumentJournal': + if (inp.registeredDocuments) { + for (const docRef of inp.registeredDocuments) { + const docName = docRef.split('.').pop(); + deps.push({ type: 'Document', name: docName, dsl: { type: 'Document', name: docName } }); + } + } + break; + } + } + return deps; +} + +// ─── Stub creation ────────────────────────────────────────────────────────── + +function makeStubDSL(type, name) { + switch (type) { + case 'Catalog': return { type: 'Catalog', name }; + case 'Document': return { type: 'Document', name }; + case 'Enum': return { type: 'Enum', name, values: ['Значение1'] }; + case 'InformationRegister': return { type: 'InformationRegister', name, dimensions: ['Ключ: String(10)'] }; + case 'AccumulationRegister': return { type: 'AccumulationRegister', name, dimensions: ['Ключ: String(10)'], resources: ['Значение: Number(15,2)'] }; + case 'ChartOfAccounts': return { type: 'ChartOfAccounts', name, codeLength: 4, descriptionLength: 100, maxExtDimensionCount: 0 }; + case 'ChartOfCharacteristicTypes': return { type: 'ChartOfCharacteristicTypes', name, codeLength: 9, descriptionLength: 100 }; + case 'ChartOfCalculationTypes': return { type: 'ChartOfCalculationTypes', name, codeLength: 9, descriptionLength: 100 }; + case 'CommonModule': return { type: 'CommonModule', name, server: true }; + case 'BusinessProcess': return { type: 'BusinessProcess', name }; + case 'Task': return { type: 'Task', name }; + case 'ExchangePlan': return { type: 'ExchangePlan', name, codeLength: 9, descriptionLength: 100 }; + case 'Role': return { type: 'Role', name: name }; + case 'Subsystem': return null; // Subsystems need special handling + default: return null; + } +} + +const TYPE_TO_PREFIX = { + Catalog: 'Catalog', Document: 'Document', Enum: 'Enum', Constant: 'Constant', + CommonModule: 'CommonModule', DataProcessor: 'DataProcessor', Report: 'Report', + InformationRegister: 'InformationRegister', AccumulationRegister: 'AccumulationRegister', + AccountingRegister: 'AccountingRegister', CalculationRegister: 'CalculationRegister', + ChartOfAccounts: 'ChartOfAccounts', ChartOfCharacteristicTypes: 'ChartOfCharacteristicTypes', + ChartOfCalculationTypes: 'ChartOfCalculationTypes', BusinessProcess: 'BusinessProcess', + Task: 'Task', ExchangePlan: 'ExchangePlan', DocumentJournal: 'DocumentJournal', + EventSubscription: 'EventSubscription', ScheduledJob: 'ScheduledJob', + DefinedType: 'DefinedType', HTTPService: 'HTTPService', WebService: 'WebService', + Subsystem: 'Subsystem', Role: 'Role', +}; + +const TYPE_TO_DIR = { + Catalog: 'Catalogs', Document: 'Documents', Enum: 'Enums', Constant: 'Constants', + CommonModule: 'CommonModules', DataProcessor: 'DataProcessors', Report: 'Reports', + InformationRegister: 'InformationRegisters', AccumulationRegister: 'AccumulationRegisters', + AccountingRegister: 'AccountingRegisters', CalculationRegister: 'CalculationRegisters', + ChartOfAccounts: 'ChartsOfAccounts', ChartOfCharacteristicTypes: 'ChartsOfCharacteristicTypes', + ChartOfCalculationTypes: 'ChartsOfCalculationTypes', BusinessProcess: 'BusinessProcesses', + Task: 'Tasks', ExchangePlan: 'ExchangePlans', DocumentJournal: 'DocumentJournals', + EventSubscription: 'EventSubscriptions', ScheduledJob: 'ScheduledJobs', + DefinedType: 'DefinedTypes', HTTPService: 'HTTPServices', WebService: 'WebServices', + Subsystem: 'Subsystems', Role: 'Roles', +}; + +// ─── Auto-detect objects in config dir for cf-edit ────────────────────────── + +function scanConfigObjects(configDir) { + const objects = []; + // DIR_TO_TYPE: reverse mapping of TYPE_TO_DIR + const DIR_TO_TYPE = {}; + for (const [type, dir] of Object.entries(TYPE_TO_DIR)) DIR_TO_TYPE[dir] = type; + + for (const dir of readdirSync(configDir)) { + const type = DIR_TO_TYPE[dir]; + if (!type) continue; + const fullDir = join(configDir, dir); + if (!statSync(fullDir).isDirectory()) continue; + for (const item of readdirSync(fullDir)) { + // Object = either dir or .xml file (for flat objects like DefinedTypes) + if (statSync(join(fullDir, item)).isDirectory()) { + objects.push({ type, name: item }); + } else if (item.endsWith('.xml')) { + const name = item.replace('.xml', ''); + // Avoid duplicates: if dir "Foo" exists and "Foo.xml" too, skip the xml + if (!existsSync(join(fullDir, name))) { + objects.push({ type, name }); + } + } + } + } + return objects; +} + +// ─── Build skill args from _skill.json mapping ───────────────────────────── + +function buildSkillArgs(skillConfig, caseData, workDir, inputFile, runtime) { + const args = []; + const scriptPath = resolveScript(skillConfig.script, runtime); + + for (const mapping of skillConfig.args) { + args.push(mapping.flag); + switch (mapping.from) { + case 'inputFile': + args.push(inputFile || ''); + break; + case 'workDir': + args.push(workDir); + break; + case 'workPath': { + const field = mapping.field || 'objectPath'; + const val = caseData.params?.[field] ?? caseData[field] ?? ''; + args.push(join(workDir, val)); + break; + } + case 'switch': + args.pop(); + if (caseData[mapping.flag.replace(/^-/, '')] !== false) args.push(mapping.flag); + break; + default: + if (mapping.from.startsWith('case.')) { + const field = mapping.from.slice(5); + args.push(String(caseData.params?.[field] ?? caseData[field] ?? '')); + } else if (mapping.from === 'literal') { + args.push(mapping.value || ''); + } + } + } + if (caseData.args_extra) args.push(...caseData.args_extra); + return { scriptPath, args }; +} + +// ─── Execute preRun steps ─────────────────────────────────────────────────── + +function runPreSteps(preRun, workDir, runtime, log) { + if (!preRun) return; + for (const step of preRun) { + const preArgs = []; + for (const [flag, value] of Object.entries(step.args || {})) { + preArgs.push(flag); + if (value === true || value === '') continue; + preArgs.push(String(value).replace('{workDir}', workDir).replace('{inputFile}', '')); + } + let preInputFile = null; + if (step.input) { + preInputFile = join(workDir, '__pre_input.json'); + writeFileSync(preInputFile, JSON.stringify(step.input, null, 2), 'utf8'); + for (let i = 0; i < preArgs.length; i++) { + if (preArgs[i] === '') preArgs[i] = preInputFile; + } + } + const stepName = step.script.split('/').pop(); + try { + execSkill(runtime, step.script, preArgs); + log(`preRun: ${stepName}`, true); + } catch (e) { + log(`preRun: ${stepName}`, false, e.stderr || e.message); + throw new Error(`preRun "${step.script}" failed: ${(e.stderr || e.message).substring(0, 500)}`); + } + if (preInputFile && existsSync(preInputFile)) rmSync(preInputFile); + } +} + +// ─── Skills that DON'T produce loadable configs ───────────────────────────── +// These produce standalone files (SKD templates, MXL templates) that can't be +// loaded into platform without wrapping in a container object. + +// Standalone file skills — produce files (not configs), platform load = just run script +const STANDALONE_SKILLS = new Set([ + 'skd-compile', 'skd-edit', 'skd-info', 'skd-validate', + 'mxl-compile', 'mxl-decompile', 'mxl-info', 'mxl-validate', +]); + +// EPF/ERF skills — need epf-build to verify, not LoadConfigFromFiles +const EPF_SKILLS = new Set([ + 'epf-init', 'epf-add-form', 'erf-init', 'template-add', 'help-add', +]); + +// cf-init produces a config dir — verify by loading the created config +const CONFIG_INIT_SKILLS = new Set(['cf-init']); + +// ─── Main verification pipeline ──────────────────────────────────────────── + +async function verifyCase(skillName, caseName, skillConfig, caseData, opts) { + const result = { + skill: skillName, case: caseName, name: caseData.name || caseName, + passed: false, steps: [], errors: [], warnings: [], workDir: null, + }; + + const workDir = mkdtempSync(join(tmpdir(), `verify-${skillName}-${caseName}-`)); + result.workDir = workDir; + + const log = (step, ok, detail) => { + result.steps.push({ step, ok, detail: detail?.substring(0, 2000) }); + if (opts.verbose) { + const icon = ok ? '\u2713' : '\u2717'; + console.log(` ${icon} ${step}${detail ? ': ' + detail.substring(0, 200) : ''}`); + } + }; + + // Determine config dir + const setupType = skillConfig.setup || 'empty-config'; + const isStandalone = STANDALONE_SKILLS.has(skillName); + const isEpf = EPF_SKILLS.has(skillName); + const isCfInit = CONFIG_INIT_SKILLS.has(skillName); + // For 'empty-config': workDir is the config (setup creates it) + // For cf-init: workDir becomes the config after the script runs + // For 'none' + non-special: no config (standalone/EPF) + const configDir = (setupType === 'empty-config' || isCfInit) ? workDir : null; + + try { + // ── Step 1: Setup (cf-init for empty-config, nothing for 'none') ── + // Skip setup for cf-init skill — the test itself creates the config + if (configDir && setupType === 'empty-config' && !CONFIG_INIT_SKILLS.has(skillName)) { + try { + execSkill(opts.runtime, 'cf-init/scripts/cf-init', ['-Name', 'VerifyTest', '-OutputDir', workDir]); + log('cf-init', true); + } catch (e) { + log('cf-init', false, e.stderr || e.message); + result.errors.push(`cf-init failed: ${(e.stderr || e.message).substring(0, 500)}`); + return result; + } + } + + // ── Step 2: Dependency stubs ── + // Collect all inputs: from caseData.input AND from preRun steps + const allInputs = []; + if (caseData.input && (caseData.input.type || Array.isArray(caseData.input))) { + const inputs = Array.isArray(caseData.input) ? caseData.input : [caseData.input]; + allInputs.push(...inputs.filter(i => i.type)); + } + // Also scan preRun inputs for type refs (D3 fix) + if (caseData.preRun) { + for (const step of caseData.preRun) { + if (step.input && step.input.type) allInputs.push(step.input); + if (Array.isArray(step.input)) allInputs.push(...step.input.filter(i => i && i.type)); + } + } + + if (configDir && allInputs.length > 0) { + const mainNames = new Set(allInputs.map(i => `${i.type}.${i.name}`)); + + // Structural deps + const structDeps = getStructuralDeps(caseData.input || {}); + const structDSLs = new Map(); + const structPostEdits = new Map(); + for (const dep of structDeps) { + const key = `${dep.type}.${dep.name}`; + if (dep.dsl) structDSLs.set(key, dep.dsl); + if (dep.postEdit) structPostEdits.set(key, dep.postEdit); + } + + // Type refs from ALL inputs (main + preRun) + const allRefs = new Map(); + for (const inp of allInputs) { + for (const [key, ref] of extractTypeRefs(inp)) { + if (!mainNames.has(key)) allRefs.set(key, ref); + } + } + for (const dep of structDeps) { + const key = `${dep.type}.${dep.name}`; + if (!mainNames.has(key) && !allRefs.has(key)) allRefs.set(key, { type: dep.type, name: dep.name }); + } + + // Create stubs + for (const [key, ref] of allRefs) { + const stubDSL = structDSLs.get(key) || makeStubDSL(ref.type, ref.name); + if (!stubDSL) { result.warnings.push(`Cannot create stub for ${key}`); continue; } + try { + const stubFile = join(workDir, `__stub.json`); + writeFileSync(stubFile, JSON.stringify(stubDSL, null, 2), 'utf8'); + execSkill(opts.runtime, 'meta-compile/scripts/meta-compile', ['-JsonPath', stubFile, '-OutputDir', configDir]); + log(`stub: ${key}`, true); + } catch (e) { + log(`stub: ${key}`, false, e.stderr || e.message); + result.warnings.push(`Stub failed: ${key}`); + } + + // Post-edit (e.g. add-registerRecord) + const edits = structPostEdits.get(key); + if (edits) { + const dir = TYPE_TO_DIR[ref.type]; + const objPath = dir ? join(configDir, dir, ref.name) : null; + if (objPath && existsSync(objPath)) { + for (const edit of edits) { + try { + execSkill(opts.runtime, 'meta-edit/scripts/meta-edit', + ['-ObjectPath', objPath, '-Operation', edit.op, '-Value', edit.val]); + log(`postEdit: ${key}`, true, `${edit.op} ${edit.val}`); + } catch (e) { + log(`postEdit: ${key}`, false, e.stderr || e.message); + result.warnings.push(`PostEdit failed: ${key}`); + } + } + } + } + } + } + + // ── Step 3: preRun steps ── + try { + runPreSteps(caseData.preRun, workDir, opts.runtime, log); + } catch (e) { + result.errors.push(e.message); + return result; + } + + // ── Step 4: Main skill script ── + let inputFile = null; + if (caseData.input !== undefined) { + inputFile = join(workDir, '__input.json'); + writeFileSync(inputFile, JSON.stringify(caseData.input, null, 2), 'utf8'); + } + + try { + const { args } = buildSkillArgs(skillConfig, caseData, workDir, inputFile, opts.runtime); + const output = execSkill(opts.runtime, skillConfig.script, args); + const lastLine = output.trim().split('\n').pop(); + log(skillName, true, lastLine); + } catch (e) { + const detail = (e.stderr || e.stdout || e.message).trim(); + log(skillName, false, detail); + result.errors.push(`${skillName} failed: ${detail.substring(0, 500)}`); + return result; + } + if (inputFile && existsSync(inputFile)) rmSync(inputFile); + + // ── Step 5: Determine verification strategy ── + if (isStandalone) { + result.passed = true; + log('platform-load', true, 'skipped (standalone file, not a config)'); + return result; + } + + if (isEpf) { + // EPF/ERF skills: script succeeded → mark passed. + // Full build verification (epf-build + platform load) is covered by integration tests. + result.passed = true; + log('platform-load', true, 'skipped (EPF — verified by integration/platform-epf)'); + return result; + } + + if (CONFIG_INIT_SKILLS.has(skillName)) { + // cf-init: the script already created the config in workDir, + // but we called cf-init in Step 1 already. For cf-init tests, + // the MAIN script IS cf-init, so workDir = the new config. + // It should be loadable as-is. + } + + if (!configDir) { + // No config to load — setup was 'none' and not EPF/standalone + result.passed = true; + return result; + } + + // ── Step 6: Auto-detect and register objects in ChildObjects ── + const allObjects = scanConfigObjects(configDir); + const cfEditOps = []; + for (const obj of allObjects) { + const prefix = TYPE_TO_PREFIX[obj.type]; + if (prefix) cfEditOps.push({ operation: 'add-childObject', value: `${prefix}.${obj.name}` }); + } + + if (cfEditOps.length > 0) { + try { + const editFile = join(workDir, '__cf-edit.json'); + writeFileSync(editFile, JSON.stringify(cfEditOps, null, 2), 'utf8'); + execSkill(opts.runtime, 'cf-edit/scripts/cf-edit', ['-ConfigPath', configDir, '-DefinitionFile', editFile]); + log('cf-edit', true, `${cfEditOps.length} objects`); + } catch (e) { + log('cf-edit', false, e.stderr || e.message); + result.errors.push(`cf-edit failed: ${(e.stderr || e.message).substring(0, 500)}`); + return result; + } + } + + // ── Step 7: Platform load ── + const dbDir = join(workDir, 'testdb'); + + try { + execSkill(opts.runtime, 'db-create/scripts/db-create', ['-V8Path', opts.v8ctx.v8path, '-InfoBasePath', dbDir]); + log('db-create', true); + } catch (e) { + log('db-create', false, e.stderr || e.message); + result.errors.push(`db-create failed: ${(e.stderr || e.message).substring(0, 500)}`); + return result; + } + + try { + execSkill(opts.runtime, 'db-load-xml/scripts/db-load-xml', + ['-V8Path', opts.v8ctx.v8path, '-InfoBasePath', dbDir, '-ConfigDir', configDir], 180_000); + log('db-load-xml', true); + } catch (e) { + const detail = (e.stderr || e.stdout || e.message).trim(); + log('db-load-xml', false, detail); + result.errors.push(`LoadConfigFromFiles failed: ${detail.substring(0, 1000)}`); + return result; + } + + try { + execSkill(opts.runtime, 'db-update/scripts/db-update', + ['-V8Path', opts.v8ctx.v8path, '-InfoBasePath', dbDir], 180_000); + log('db-update', true); + } catch (e) { + const detail = (e.stderr || e.stdout || e.message).trim(); + log('db-update', false, detail); + result.errors.push(`UpdateDBCfg failed: ${detail.substring(0, 1000)}`); + return result; + } + + result.passed = true; + } catch (e) { + result.errors.push(`Unexpected error: ${e.message}`); + } finally { + if (!opts.keep) { + try { rmSync(workDir, { recursive: true, force: true }); } catch {} + result.workDir = '(cleaned)'; + } + } + + return result; +} + +// ─── Discovery ────────────────────────────────────────────────────────────── + +// Default skills to verify when no --skill given +const DEFAULT_SKILLS = [ + 'meta-compile', 'form-compile', 'form-add', 'form-edit', + 'role-compile', 'subsystem-compile', 'subsystem-edit', + 'cf-init', 'cf-edit', 'meta-edit', 'interface-edit', + 'epf-init', 'epf-add-form', 'template-add', 'help-add', + 'cfe-init', 'cfe-borrow', 'cfe-patch-method', + 'skd-compile', 'skd-edit', 'mxl-compile', +]; + +function discoverCases(skillFilter, caseFilter) { + const results = []; + const skillDirs = skillFilter ? [skillFilter] : DEFAULT_SKILLS; + + for (const skillDir of skillDirs) { + const skillPath = join(CASES, skillDir); + if (!existsSync(skillPath)) continue; + + const skillJsonPath = join(skillPath, '_skill.json'); + if (!existsSync(skillJsonPath)) continue; + const skillConfig = JSON.parse(readFileSync(skillJsonPath, 'utf8')); + + // Skip skills that don't have snapshots (read-only, info, validate) + if (!existsSync(join(skillPath, 'snapshots'))) continue; + + for (const file of readdirSync(skillPath)) { + if (file.startsWith('_') || !file.endsWith('.json')) continue; + const caseName = file.replace(/\.json$/, ''); + + if (caseFilter && caseName !== caseFilter) continue; + + const caseData = JSON.parse(readFileSync(join(skillPath, file), 'utf8')); + + // Skip error cases + if (caseName.startsWith('error-')) continue; + + // Skip cases without input AND without preRun AND without params (truly read-only) + if (caseData.input === undefined && !caseData.preRun && !caseData.params) continue; + + results.push({ skill: skillDir, caseName, caseData, skillConfig }); + } + } + return results; +} + +// ─── Report ───────────────────────────────────────────────────────────────── + +function writeReport(results) { + mkdirSync(REPORT_DIR, { recursive: true }); + + const lines = [ + `# Snapshot Verification Report`, + ``, + `Date: ${new Date().toISOString().split('T')[0]}`, + `Total: ${results.length} | Passed: ${results.filter(r => r.passed).length} | Failed: ${results.filter(r => !r.passed).length}`, + ``, + ]; + + lines.push('| Skill | Case | Status | Error |'); + lines.push('|-------|------|--------|-------|'); + for (const r of results) { + const status = r.passed ? 'OK' : 'FAIL'; + const error = r.errors.length > 0 ? r.errors[0].substring(0, 100).replace(/\|/g, '\\|').replace(/\n/g, ' ') : ''; + lines.push(`| ${r.skill} | ${r.case} | ${status} | ${error} |`); + } + + const failures = results.filter(r => !r.passed); + if (failures.length > 0) { + lines.push('', '## Findings', ''); + for (const r of failures) { + lines.push(`### ${r.skill}/${r.case}: ${r.name}`); + lines.push(''); + lines.push('**Steps:**'); + for (const s of r.steps) { + lines.push(`- ${s.ok ? '\u2713' : '\u2717'} ${s.step}${s.detail ? ': ' + s.detail.substring(0, 300) : ''}`); + } + if (r.warnings.length > 0) { + lines.push('', '**Warnings:**'); + for (const w of r.warnings) lines.push(`- ${w}`); + } + lines.push('', '**Errors:**'); + for (const e of r.errors) lines.push('```', e, '```'); + lines.push(''); + lines.push('**Classification:** '); + lines.push('**Action:** '); + lines.push(''); + } + } + + const withWarnings = results.filter(r => r.passed && r.warnings.length > 0); + if (withWarnings.length > 0) { + lines.push('', '## Warnings (passed with notes)', ''); + for (const r of withWarnings) { + lines.push(`### ${r.skill}/${r.case}`); + for (const w of r.warnings) lines.push(`- ${w}`); + lines.push(''); + } + } + + const reportPath = join(REPORT_DIR, 'REPORT.md'); + writeFileSync(reportPath, lines.join('\n'), 'utf8'); + console.log(`\nReport written to: ${reportPath}`); +} + +// ─── Main ─────────────────────────────────────────────────────────────────── + +async function main() { + const opts = parseArgs(process.argv); + + const v8ctx = loadV8Context(); + if (!v8ctx) { + console.error('ERROR: 1C platform not found. Check .v8-project.json'); + process.exit(1); + } + opts.v8ctx = v8ctx; + console.log(`Platform: ${v8ctx.v8exe}`); + + const cases = discoverCases(opts.skill, opts.caseName); + if (cases.length === 0) { + console.error('No cases found.'); + process.exit(1); + } + console.log(`Found ${cases.length} case(s) to verify.\n`); + + const results = []; + for (const { skill, caseName, caseData, skillConfig } of cases) { + const label = `${skill}/${caseName}`; + if (opts.verbose) console.log(` ${label}: ${caseData.name || ''}`); + else process.stdout.write(` ${label}...`); + + const t0 = performance.now(); + const result = await verifyCase(skill, caseName, skillConfig, caseData, opts); + const elapsed = ((performance.now() - t0) / 1000).toFixed(1); + + if (!opts.verbose) { + const icon = result.passed ? '\u2713' : '\u2717'; + console.log(` ${icon} (${elapsed}s)${result.errors.length ? ' — ' + result.errors[0].substring(0, 80) : ''}`); + } else { + console.log(` → ${result.passed ? 'PASS' : 'FAIL'} (${elapsed}s)\n`); + } + + results.push(result); + } + + const passed = results.filter(r => r.passed).length; + const failed = results.filter(r => !r.passed).length; + console.log(`\n${'='.repeat(60)}`); + console.log(`Results: ${passed} passed, ${failed} failed out of ${results.length}`); + + writeReport(results); + process.exit(failed > 0 ? 1 : 0); +} + +main().catch(e => { console.error(e); process.exit(1); }); From b0fdc32053c9c2da5657e99a9ae713d25ffb1e90 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Apr 2026 19:43:42 +0300 Subject: [PATCH 02/94] =?UTF-8?q?feat(tests):=20verify-snapshots=20v0.3=20?= =?UTF-8?q?=E2=80=94=20CFE=20support,=20preRun=20ref=20scanning,=20full=20?= =?UTF-8?q?coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add two-stage CFE pipeline: load base config → load extension - Scan preRun inputs for type refs (fixes D3: meta-edit/add-ts-attribute) - Support args-only cases (cf-init, form-add, epf-init, template-add, etc.) - Add InformationRegister stubs with dimension (fixes D6) - Results: 134/145 verified (16 new CFE cases all pass) Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/skills/verify-snapshots.mjs | 99 ++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/tests/skills/verify-snapshots.mjs b/tests/skills/verify-snapshots.mjs index 73dce95e..12dde8ce 100644 --- a/tests/skills/verify-snapshots.mjs +++ b/tests/skills/verify-snapshots.mjs @@ -317,6 +317,11 @@ const EPF_SKILLS = new Set([ 'epf-init', 'epf-add-form', 'erf-init', 'template-add', 'help-add', ]); +// CFE skills — two-stage load: base config → extension +const CFE_SKILLS = new Set([ + 'cfe-init', 'cfe-borrow', 'cfe-patch-method', +]); + // cf-init produces a config dir — verify by loading the created config const CONFIG_INIT_SKILLS = new Set(['cf-init']); @@ -474,13 +479,103 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) { } if (isEpf) { - // EPF/ERF skills: script succeeded → mark passed. - // Full build verification (epf-build + platform load) is covered by integration tests. result.passed = true; log('platform-load', true, 'skipped (EPF — verified by integration/platform-epf)'); return result; } + if (CFE_SKILLS.has(skillName)) { + // CFE: two-stage load — base config first, then extension + const extDir = join(workDir, 'ext'); + const baseConfigDir = workDir; // preRun puts base config directly in workDir + const dbDir = join(workDir, 'testdb'); + + // Register base config objects + const baseObjects = scanConfigObjects(baseConfigDir); + const baseCfEditOps = baseObjects + .filter(o => TYPE_TO_PREFIX[o.type]) + .map(o => ({ operation: 'add-childObject', value: `${TYPE_TO_PREFIX[o.type]}.${o.name}` })); + if (baseCfEditOps.length > 0) { + try { + const editFile = join(workDir, '__cf-edit-base.json'); + writeFileSync(editFile, JSON.stringify(baseCfEditOps, null, 2), 'utf8'); + execSkill(opts.runtime, 'cf-edit/scripts/cf-edit', ['-ConfigPath', baseConfigDir, '-DefinitionFile', editFile]); + log('cf-edit (base)', true, `${baseCfEditOps.length} objects`); + } catch (e) { + log('cf-edit (base)', false, e.stderr || e.message); + result.errors.push(`cf-edit base failed: ${(e.stderr || e.message).substring(0, 500)}`); + return result; + } + } + + // Create DB + load base config + try { + execSkill(opts.runtime, 'db-create/scripts/db-create', ['-V8Path', opts.v8ctx.v8path, '-InfoBasePath', dbDir]); + log('db-create', true); + } catch (e) { + log('db-create', false, e.stderr || e.message); + result.errors.push(`db-create failed: ${(e.stderr || e.message).substring(0, 500)}`); + return result; + } + + try { + execSkill(opts.runtime, 'db-load-xml/scripts/db-load-xml', + ['-V8Path', opts.v8ctx.v8path, '-InfoBasePath', dbDir, '-ConfigDir', baseConfigDir], 180_000); + log('db-load-xml (config)', true); + } catch (e) { + const detail = (e.stderr || e.stdout || e.message).trim(); + log('db-load-xml (config)', false, detail); + result.errors.push(`LoadConfig failed: ${detail.substring(0, 1000)}`); + return result; + } + + try { + execSkill(opts.runtime, 'db-update/scripts/db-update', + ['-V8Path', opts.v8ctx.v8path, '-InfoBasePath', dbDir], 180_000); + log('db-update (config)', true); + } catch (e) { + const detail = (e.stderr || e.stdout || e.message).trim(); + log('db-update (config)', false, detail); + result.errors.push(`UpdateDBCfg config failed: ${detail.substring(0, 1000)}`); + return result; + } + + // Load extension — detect extension name from ext/Configuration.xml + let extName = 'Extension'; + try { + const extConfigXml = readFileSync(join(extDir, 'Configuration.xml'), 'utf8'); + const nameMatch = extConfigXml.match(/([^<]+)<\/Name>/); + if (nameMatch) extName = nameMatch[1]; + } catch {} + + if (existsSync(extDir)) { + try { + execSkill(opts.runtime, 'db-load-xml/scripts/db-load-xml', + ['-V8Path', opts.v8ctx.v8path, '-InfoBasePath', dbDir, '-ConfigDir', extDir, '-Extension', extName], 180_000); + log('db-load-xml (ext)', true); + } catch (e) { + const detail = (e.stderr || e.stdout || e.message).trim(); + log('db-load-xml (ext)', false, detail); + result.errors.push(`LoadExtension failed: ${detail.substring(0, 1000)}`); + return result; + } + + try { + execSkill(opts.runtime, 'db-update/scripts/db-update', + ['-V8Path', opts.v8ctx.v8path, '-InfoBasePath', dbDir, '-Extension', extName], 180_000); + log('db-update (ext)', true); + } catch (e) { + const detail = (e.stderr || e.stdout || e.message).trim(); + log('db-update (ext)', false, detail); + result.errors.push(`UpdateDBCfg ext failed: ${detail.substring(0, 1000)}`); + return result; + } + } + + result.passed = true; + return result; + } + if (CONFIG_INIT_SKILLS.has(skillName)) { // cf-init: the script already created the config in workDir, // but we called cf-init in Step 1 already. For cf-init tests, From 3bd69baae6b1c6e931c7bbba57947a9a73e95f2f Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Apr 2026 19:53:58 +0300 Subject: [PATCH 03/94] fix(form-validate): add ExternalDataProcessorObject/ExternalReportObject to valid cfg prefixes Check 12 was flagging cfg:ExternalDataProcessorObject.X as "unrecognized cfg prefix", but this is a valid XDTO type for external data processor (EPF) forms. Found via snapshot verification against epf-add-form snapshots. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../form-validate/scripts/form-validate.ps1 | 27 +++++++++++++++++-- .../form-validate/scripts/form-validate.py | 20 ++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/.claude/skills/form-validate/scripts/form-validate.ps1 b/.claude/skills/form-validate/scripts/form-validate.ps1 index 268266a4..d66ffcfc 100644 --- a/.claude/skills/form-validate/scripts/form-validate.ps1 +++ b/.claude/skills/form-validate/scripts/form-validate.ps1 @@ -1,4 +1,4 @@ -# form-validate v1.2 — Validate 1C managed form +# form-validate v1.3 — Validate 1C managed form # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -58,6 +58,20 @@ $nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core") $root = $xmlDoc.DocumentElement +# --- Detect context: config vs EPF/ERF --- +# Walk up from FormPath looking for Configuration.xml → config context +# No Configuration.xml → external data processor / report (EPF/ERF) +$script:isConfigContext = $false +$walkDir = Split-Path (Resolve-Path $FormPath) -Parent +for ($i = 0; $i -lt 15; $i++) { + if (-not $walkDir -or $walkDir -eq (Split-Path $walkDir)) { break } + if (Test-Path (Join-Path $walkDir "Configuration.xml")) { + $script:isConfigContext = $true + break + } + $walkDir = Split-Path $walkDir +} + # --- Counters --- $errors = 0 @@ -696,6 +710,7 @@ $validCfgPrefixes = @( "ChartOfCharacteristicTypesObject","ChartOfCharacteristicTypesRef" "ConstantsSet","DataProcessorObject","DocumentObject","DocumentRef" "DynamicList","EnumRef","ExchangePlanObject","ExchangePlanRef" + "ExternalDataProcessorObject","ExternalReportObject" "InformationRegisterRecordManager","InformationRegisterRecordSet" "ReportObject","TaskObject","TaskRef" ) @@ -719,7 +734,15 @@ if (-not $stopped) { $cfgVal = $Matches[1] if ($cfgVal -eq "DynamicList") { continue } if ($cfgVal -match '^([^.]+)\.') { - if ($Matches[1] -in $validCfgPrefixes) { continue } + $pfx = $Matches[1] + if ($pfx -in $validCfgPrefixes) { + # ExternalDataProcessorObject/ExternalReportObject valid only for EPF/ERF, not config + if ($script:isConfigContext -and ($pfx -eq "ExternalDataProcessorObject" -or $pfx -eq "ExternalReportObject")) { + Report-Warn "12. Type '$tv': External* type in configuration context (use DataProcessorObject/ReportObject instead)" + $typeOk = $false + } + continue + } } Report-Warn "12. Type '$tv': unrecognized cfg prefix" $typeOk = $false diff --git a/.claude/skills/form-validate/scripts/form-validate.py b/.claude/skills/form-validate/scripts/form-validate.py index b0a753c6..0234ed30 100644 --- a/.claude/skills/form-validate/scripts/form-validate.py +++ b/.claude/skills/form-validate/scripts/form-validate.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-validate v1.2 — Validate 1C managed form +# form-validate v1.3 — Validate 1C managed form # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -43,6 +43,7 @@ VALID_CFG_PREFIXES = { 'ChartOfCharacteristicTypesObject', 'ChartOfCharacteristicTypesRef', 'ConstantsSet', 'DataProcessorObject', 'DocumentObject', 'DocumentRef', 'DynamicList', 'EnumRef', 'ExchangePlanObject', 'ExchangePlanRef', + 'ExternalDataProcessorObject', 'ExternalReportObject', 'InformationRegisterRecordManager', 'InformationRegisterRecordSet', 'ReportObject', 'TaskObject', 'TaskRef', } @@ -103,6 +104,18 @@ def main(): root = tree.getroot() + # Detect context: config vs EPF/ERF + is_config_context = False + walk_dir = os.path.dirname(os.path.abspath(form_path)) + for _ in range(15): + parent = os.path.dirname(walk_dir) + if parent == walk_dir: + break + if os.path.isfile(os.path.join(walk_dir, 'Configuration.xml')): + is_config_context = True + break + walk_dir = parent + errors = 0 warnings = 0 ok_count = 0 @@ -645,7 +658,10 @@ def main(): suffix = tv[4:] # after "cfg:" prefix = suffix.split(".")[0] if prefix in VALID_CFG_PREFIXES or suffix == "DynamicList": - pass # OK + # ExternalDataProcessorObject/ExternalReportObject valid only in EPF/ERF context + if is_config_context and prefix in ('ExternalDataProcessorObject', 'ExternalReportObject'): + report_warn(f'12. Type "{tv}": External* type in configuration context (use DataProcessorObject/ReportObject instead)') + type_warn_count += 1 else: report_warn(f'12. Type "{tv}": unrecognized cfg prefix') type_warn_count += 1 From d5aacc9e6036fa7f0a4a9c65aa69972eb2079c98 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Apr 2026 20:01:24 +0300 Subject: [PATCH 04/94] =?UTF-8?q?fix(form-validate):=20context-aware=20Che?= =?UTF-8?q?ck=2012=20=E2=80=94=20ExternalDataProcessorObject=20is=20error?= =?UTF-8?q?=20in=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detect config vs EPF context by walking up from FormPath looking for Configuration.xml. ExternalDataProcessorObject/ExternalReportObject are valid in EPF/ERF but cause XDTO exception in configuration context. - EPF forms: no warning (ExternalDataProcessorObject is correct) - Config forms: ERROR with hint to use DataProcessorObject/ReportObject - Fix test DSLs: compiled-form, table-form used wrong External* type Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/form-validate/scripts/form-validate.ps1 | 4 ++-- .claude/skills/form-validate/scripts/form-validate.py | 4 ++-- tests/skills/cases/form-validate/compiled-form.json | 2 +- .../DataProcessors/Валидация/Forms/Форма/Ext/Form.xml | 2 +- .../DataProcessors/ВалТабл/Forms/Форма/Ext/Form.xml | 2 +- tests/skills/cases/form-validate/table-form.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.claude/skills/form-validate/scripts/form-validate.ps1 b/.claude/skills/form-validate/scripts/form-validate.ps1 index d66ffcfc..c53a60ae 100644 --- a/.claude/skills/form-validate/scripts/form-validate.ps1 +++ b/.claude/skills/form-validate/scripts/form-validate.ps1 @@ -738,8 +738,8 @@ if (-not $stopped) { if ($pfx -in $validCfgPrefixes) { # ExternalDataProcessorObject/ExternalReportObject valid only for EPF/ERF, not config if ($script:isConfigContext -and ($pfx -eq "ExternalDataProcessorObject" -or $pfx -eq "ExternalReportObject")) { - Report-Warn "12. Type '$tv': External* type in configuration context (use DataProcessorObject/ReportObject instead)" - $typeOk = $false + Report-Error "12. Type '$tv': External* type in configuration context (use DataProcessorObject/ReportObject instead)" + $typeOk = $false; $typeInvalid++ } continue } diff --git a/.claude/skills/form-validate/scripts/form-validate.py b/.claude/skills/form-validate/scripts/form-validate.py index 0234ed30..91dac95b 100644 --- a/.claude/skills/form-validate/scripts/form-validate.py +++ b/.claude/skills/form-validate/scripts/form-validate.py @@ -660,8 +660,8 @@ def main(): if prefix in VALID_CFG_PREFIXES or suffix == "DynamicList": # ExternalDataProcessorObject/ExternalReportObject valid only in EPF/ERF context if is_config_context and prefix in ('ExternalDataProcessorObject', 'ExternalReportObject'): - report_warn(f'12. Type "{tv}": External* type in configuration context (use DataProcessorObject/ReportObject instead)') - type_warn_count += 1 + report_error(f'12. Type "{tv}": External* type in configuration context (use DataProcessorObject/ReportObject instead)') + type_invalid += 1 else: report_warn(f'12. Type "{tv}": unrecognized cfg prefix') type_warn_count += 1 diff --git a/tests/skills/cases/form-validate/compiled-form.json b/tests/skills/cases/form-validate/compiled-form.json index 6748d142..ab8faa7e 100644 --- a/tests/skills/cases/form-validate/compiled-form.json +++ b/tests/skills/cases/form-validate/compiled-form.json @@ -23,7 +23,7 @@ { "button": "Выполнить", "command": "Выполнить" } ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.Валидация", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.Валидация", "main": true }, { "name": "Поле1", "type": "string" }, { "name": "Поле2", "type": "decimal(15,2)" }, { "name": "Флаг", "type": "boolean" } diff --git a/tests/skills/cases/form-validate/snapshots/compiled-form/DataProcessors/Валидация/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-validate/snapshots/compiled-form/DataProcessors/Валидация/Forms/Форма/Ext/Form.xml index 703d3d86..c9f03296 100644 --- a/tests/skills/cases/form-validate/snapshots/compiled-form/DataProcessors/Валидация/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-validate/snapshots/compiled-form/DataProcessors/Валидация/Forms/Форма/Ext/Form.xml @@ -40,7 +40,7 @@ - cfg:ExternalDataProcessorObject.Валидация + cfg:DataProcessorObject.Валидация true diff --git a/tests/skills/cases/form-validate/snapshots/table-form/DataProcessors/ВалТабл/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-validate/snapshots/table-form/DataProcessors/ВалТабл/Forms/Форма/Ext/Form.xml index 0cd33ce3..573e505f 100644 --- a/tests/skills/cases/form-validate/snapshots/table-form/DataProcessors/ВалТабл/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-validate/snapshots/table-form/DataProcessors/ВалТабл/Forms/Форма/Ext/Form.xml @@ -35,7 +35,7 @@ - cfg:ExternalDataProcessorObject.ВалТабл + cfg:DataProcessorObject.ВалТабл true diff --git a/tests/skills/cases/form-validate/table-form.json b/tests/skills/cases/form-validate/table-form.json index e650e9c8..ef0655de 100644 --- a/tests/skills/cases/form-validate/table-form.json +++ b/tests/skills/cases/form-validate/table-form.json @@ -21,7 +21,7 @@ ]} ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.ВалТабл", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.ВалТабл", "main": true }, { "name": "Данные", "type": "ValueTable", "columns": [ { "name": "Наименование", "type": "string(150)" }, { "name": "Количество", "type": "decimal(10,3)" } From 20adf4f463de4f71cbe3ea4e62c08ba153987864 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Apr 2026 20:10:49 +0300 Subject: [PATCH 05/94] =?UTF-8?q?fix(tests):=20correct=20ExternalDataProce?= =?UTF-8?q?ssorObject=E2=86=92DataProcessorObject=20in=20config-context=20?= =?UTF-8?q?DSLs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix test DSLs that used ExternalDataProcessorObject (EPF type) for DataProcessors inside configurations. Also fix: chart-of-accounts (remove maxExtDimensionCount without ПВХТ), calculation-register (remove actionPeriod without infrastructure), document-multiple-tabparts (remove registerRecords referencing non-existent register), role-compile/explicit-rights (add dimensions to empty InformationRegister). Regenerated all affected snapshots. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../cases/form-compile/attributes-types.json | 2 +- tests/skills/cases/form-compile/commands.json | 2 +- tests/skills/cases/form-compile/events.json | 2 +- .../cases/form-compile/file-dialog.json | 2 +- tests/skills/cases/form-compile/groups.json | 2 +- .../cases/form-compile/input-fields.json | 2 +- tests/skills/cases/form-compile/pages.json | 2 +- .../Типы/Forms/Форма/Ext/Form.xml | 2 +- .../Команды/Forms/Форма/Ext/Form.xml | 2 +- .../События/Forms/Форма/Ext/Form.xml | 2 +- .../ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml | 2 +- .../СГруппами/Forms/Форма/Ext/Form.xml | 2 +- .../ПоляВвода/Forms/Форма/Ext/Form.xml | 2 +- .../Мастер/Forms/Форма/Ext/Form.xml | 2 +- .../Таблица/Forms/Форма/Ext/Form.xml | 2 +- tests/skills/cases/form-compile/table.json | 2 +- .../skills/cases/form-edit/add-attribute.json | 2 +- tests/skills/cases/form-edit/add-command.json | 2 +- tests/skills/cases/form-edit/add-element.json | 2 +- .../form-edit/add-group-with-fields.json | 2 +- .../Реквизиты/Forms/Форма/Ext/Form.xml | 2 +- .../КомандыТест/Forms/Форма/Ext/Form.xml | 2 +- .../Тест/Forms/Форма/Ext/Form.xml | 2 +- .../Группа/Forms/Форма/Ext/Form.xml | 2 +- .../meta-compile/calculation-register.json | 2 - .../cases/meta-compile/chart-of-accounts.json | 5 +- .../document-multiple-tabparts.json | 1 - .../CalculationRegisters/Начисления.xml | 4 +- .../ChartsOfAccounts/Хозрасчетный.xml | 34 +------ .../Documents/РеализацияТоваров.xml | 4 +- .../cases/role-compile/explicit-rights.json | 4 +- .../InformationRegisters/Цены.xml | 93 ++++++++++++++++++- 32 files changed, 125 insertions(+), 70 deletions(-) diff --git a/tests/skills/cases/form-compile/attributes-types.json b/tests/skills/cases/form-compile/attributes-types.json index 869df26e..f32bb3e4 100644 --- a/tests/skills/cases/form-compile/attributes-types.json +++ b/tests/skills/cases/form-compile/attributes-types.json @@ -22,7 +22,7 @@ { "input": "Булево", "path": "Булево" } ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.Типы", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.Типы", "main": true }, { "name": "Строка", "type": "string(200)" }, { "name": "Число", "type": "decimal(10,0,nonneg)" }, { "name": "Дата", "type": "dateTime" }, diff --git a/tests/skills/cases/form-compile/commands.json b/tests/skills/cases/form-compile/commands.json index 45136914..6cef4705 100644 --- a/tests/skills/cases/form-compile/commands.json +++ b/tests/skills/cases/form-compile/commands.json @@ -23,7 +23,7 @@ { "input": "Результат", "path": "Результат", "multiLine": true, "height": 8, "readOnly": true } ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.Команды", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.Команды", "main": true }, { "name": "Результат", "type": "string" } ], "commands": [ diff --git a/tests/skills/cases/form-compile/events.json b/tests/skills/cases/form-compile/events.json index ba625ad5..05bd48b0 100644 --- a/tests/skills/cases/form-compile/events.json +++ b/tests/skills/cases/form-compile/events.json @@ -22,7 +22,7 @@ { "label": "Подсказка", "title": "Нажмите для перехода", "hyperlink": true, "on": ["Click"] } ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.События", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.События", "main": true }, { "name": "Организация", "type": "string" }, { "name": "Период", "type": "date" } ] diff --git a/tests/skills/cases/form-compile/file-dialog.json b/tests/skills/cases/form-compile/file-dialog.json index c12b848b..8853099f 100644 --- a/tests/skills/cases/form-compile/file-dialog.json +++ b/tests/skills/cases/form-compile/file-dialog.json @@ -29,7 +29,7 @@ ]} ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.ЗагрузкаИзФайла", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.ЗагрузкаИзФайла", "main": true }, { "name": "ИмяФайла", "type": "string" }, { "name": "ПерваяСтрокаЗаголовок", "type": "boolean" }, { "name": "Результат", "type": "string" } diff --git a/tests/skills/cases/form-compile/groups.json b/tests/skills/cases/form-compile/groups.json index 6b4c91d1..fa474f58 100644 --- a/tests/skills/cases/form-compile/groups.json +++ b/tests/skills/cases/form-compile/groups.json @@ -26,7 +26,7 @@ ]} ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.СГруппами", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.СГруппами", "main": true }, { "name": "Поле1", "type": "string" }, { "name": "Поле2", "type": "decimal(15,2)" }, { "name": "Поле3", "type": "date" } diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json index 44c9f237..27c45e99 100644 --- a/tests/skills/cases/form-compile/input-fields.json +++ b/tests/skills/cases/form-compile/input-fields.json @@ -24,7 +24,7 @@ { "check": "Флаг", "path": "Флаг", "title": "Включено" } ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.ПоляВвода", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.ПоляВвода", "main": true }, { "name": "ОбычноеПоле", "type": "string(100)" }, { "name": "МногострочноеПоле", "type": "string" }, { "name": "ПолеПароля", "type": "string(50)" }, diff --git a/tests/skills/cases/form-compile/pages.json b/tests/skills/cases/form-compile/pages.json index 93c23bce..c1e79c44 100644 --- a/tests/skills/cases/form-compile/pages.json +++ b/tests/skills/cases/form-compile/pages.json @@ -31,7 +31,7 @@ ]} ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.Мастер", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.Мастер", "main": true }, { "name": "Параметр1", "type": "string" }, { "name": "Итог", "type": "string" } ], diff --git a/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml index de215cc3..772b90d8 100644 --- a/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml @@ -35,7 +35,7 @@ - cfg:ExternalDataProcessorObject.Типы + cfg:DataProcessorObject.Типы true diff --git a/tests/skills/cases/form-compile/snapshots/commands/DataProcessors/Команды/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/commands/DataProcessors/Команды/Forms/Форма/Ext/Form.xml index 29373db5..1e28a424 100644 --- a/tests/skills/cases/form-compile/snapshots/commands/DataProcessors/Команды/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/commands/DataProcessors/Команды/Forms/Форма/Ext/Form.xml @@ -36,7 +36,7 @@ - cfg:ExternalDataProcessorObject.Команды + cfg:DataProcessorObject.Команды true diff --git a/tests/skills/cases/form-compile/snapshots/events/DataProcessors/События/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/events/DataProcessors/События/Forms/Форма/Ext/Form.xml index 3f09f910..e9d2fd6e 100644 --- a/tests/skills/cases/form-compile/snapshots/events/DataProcessors/События/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/events/DataProcessors/События/Forms/Форма/Ext/Form.xml @@ -47,7 +47,7 @@ - cfg:ExternalDataProcessorObject.События + cfg:DataProcessorObject.События true diff --git a/tests/skills/cases/form-compile/snapshots/file-dialog/DataProcessors/ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/file-dialog/DataProcessors/ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml index a070c812..a1f7070d 100644 --- a/tests/skills/cases/form-compile/snapshots/file-dialog/DataProcessors/ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/file-dialog/DataProcessors/ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml @@ -79,7 +79,7 @@ - cfg:ExternalDataProcessorObject.ЗагрузкаИзФайла + cfg:DataProcessorObject.ЗагрузкаИзФайла true diff --git a/tests/skills/cases/form-compile/snapshots/groups/DataProcessors/СГруппами/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/groups/DataProcessors/СГруппами/Forms/Форма/Ext/Form.xml index f7568682..4ba7c8d8 100644 --- a/tests/skills/cases/form-compile/snapshots/groups/DataProcessors/СГруппами/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/groups/DataProcessors/СГруппами/Forms/Форма/Ext/Form.xml @@ -69,7 +69,7 @@ - cfg:ExternalDataProcessorObject.СГруппами + cfg:DataProcessorObject.СГруппами true diff --git a/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml index cb84824e..d0cc3653 100644 --- a/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml @@ -91,7 +91,7 @@ - cfg:ExternalDataProcessorObject.ПоляВвода + cfg:DataProcessorObject.ПоляВвода true diff --git a/tests/skills/cases/form-compile/snapshots/pages/DataProcessors/Мастер/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/pages/DataProcessors/Мастер/Forms/Форма/Ext/Form.xml index d1cea7e0..b826493d 100644 --- a/tests/skills/cases/form-compile/snapshots/pages/DataProcessors/Мастер/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/pages/DataProcessors/Мастер/Forms/Форма/Ext/Form.xml @@ -81,7 +81,7 @@ - cfg:ExternalDataProcessorObject.Мастер + cfg:DataProcessorObject.Мастер true diff --git a/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml index 1e377ee7..8cffd9b3 100644 --- a/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml @@ -41,7 +41,7 @@ - cfg:ExternalDataProcessorObject.Таблица + cfg:DataProcessorObject.Таблица true diff --git a/tests/skills/cases/form-compile/table.json b/tests/skills/cases/form-compile/table.json index 025d12aa..bdd3e9b3 100644 --- a/tests/skills/cases/form-compile/table.json +++ b/tests/skills/cases/form-compile/table.json @@ -23,7 +23,7 @@ ]} ], "attributes": [ - { "name": "Объект", "type": "ExternalDataProcessorObject.Таблица", "main": true }, + { "name": "Объект", "type": "DataProcessorObject.Таблица", "main": true }, { "name": "Данные", "type": "ValueTable", "columns": [ { "name": "Дата", "type": "date" }, { "name": "Сумма", "type": "decimal(15,2)" }, diff --git a/tests/skills/cases/form-edit/add-attribute.json b/tests/skills/cases/form-edit/add-attribute.json index b2c2c006..42df59f7 100644 --- a/tests/skills/cases/form-edit/add-attribute.json +++ b/tests/skills/cases/form-edit/add-attribute.json @@ -14,7 +14,7 @@ "script": "form-compile/scripts/form-compile", "input": { "title": "Реквизиты", - "attributes": [{ "name": "Объект", "type": "ExternalDataProcessorObject.Реквизиты", "main": true }] + "attributes": [{ "name": "Объект", "type": "DataProcessorObject.Реквизиты", "main": true }] }, "args": { "-JsonPath": "{inputFile}", "-OutputPath": "{workDir}/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml" } } diff --git a/tests/skills/cases/form-edit/add-command.json b/tests/skills/cases/form-edit/add-command.json index d27c2924..d5c9dad5 100644 --- a/tests/skills/cases/form-edit/add-command.json +++ b/tests/skills/cases/form-edit/add-command.json @@ -14,7 +14,7 @@ "script": "form-compile/scripts/form-compile", "input": { "title": "Команды", - "attributes": [{ "name": "Объект", "type": "ExternalDataProcessorObject.КомандыТест", "main": true }] + "attributes": [{ "name": "Объект", "type": "DataProcessorObject.КомандыТест", "main": true }] }, "args": { "-JsonPath": "{inputFile}", "-OutputPath": "{workDir}/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml" } } diff --git a/tests/skills/cases/form-edit/add-element.json b/tests/skills/cases/form-edit/add-element.json index e29d0c7a..6cd60356 100644 --- a/tests/skills/cases/form-edit/add-element.json +++ b/tests/skills/cases/form-edit/add-element.json @@ -14,7 +14,7 @@ "script": "form-compile/scripts/form-compile", "input": { "title": "Тест", - "attributes": [{ "name": "Объект", "type": "ExternalDataProcessorObject.Тест", "main": true }], + "attributes": [{ "name": "Объект", "type": "DataProcessorObject.Тест", "main": true }], "elements": [ { "input": "Поле1", "path": "Поле1", "title": "Поле 1" } ] diff --git a/tests/skills/cases/form-edit/add-group-with-fields.json b/tests/skills/cases/form-edit/add-group-with-fields.json index c98f0bf7..dc799c54 100644 --- a/tests/skills/cases/form-edit/add-group-with-fields.json +++ b/tests/skills/cases/form-edit/add-group-with-fields.json @@ -14,7 +14,7 @@ "script": "form-compile/scripts/form-compile", "input": { "title": "Группа", - "attributes": [{ "name": "Объект", "type": "ExternalDataProcessorObject.Группа", "main": true }], + "attributes": [{ "name": "Объект", "type": "DataProcessorObject.Группа", "main": true }], "elements": [ { "input": "Поле1", "path": "Поле1", "title": "Существующее поле" } ] diff --git a/tests/skills/cases/form-edit/snapshots/add-attribute/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-edit/snapshots/add-attribute/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml index ab2bfdf3..27422818 100644 --- a/tests/skills/cases/form-edit/snapshots/add-attribute/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-edit/snapshots/add-attribute/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml @@ -13,7 +13,7 @@ - cfg:ExternalDataProcessorObject.Реквизиты + cfg:DataProcessorObject.Реквизиты true diff --git a/tests/skills/cases/form-edit/snapshots/add-command/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-edit/snapshots/add-command/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml index 3b02c66d..211cf302 100644 --- a/tests/skills/cases/form-edit/snapshots/add-command/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-edit/snapshots/add-command/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml @@ -20,7 +20,7 @@ - cfg:ExternalDataProcessorObject.КомандыТест + cfg:DataProcessorObject.КомандыТест true diff --git a/tests/skills/cases/form-edit/snapshots/add-element/DataProcessors/Тест/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-edit/snapshots/add-element/DataProcessors/Тест/Forms/Форма/Ext/Form.xml index b4a71915..21b0561d 100644 --- a/tests/skills/cases/form-edit/snapshots/add-element/DataProcessors/Тест/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-edit/snapshots/add-element/DataProcessors/Тест/Forms/Форма/Ext/Form.xml @@ -37,7 +37,7 @@ - cfg:ExternalDataProcessorObject.Тест + cfg:DataProcessorObject.Тест true diff --git a/tests/skills/cases/form-edit/snapshots/add-group-with-fields/DataProcessors/Группа/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-edit/snapshots/add-group-with-fields/DataProcessors/Группа/Forms/Форма/Ext/Form.xml index 7c45f847..3b4bcfd6 100644 --- a/tests/skills/cases/form-edit/snapshots/add-group-with-fields/DataProcessors/Группа/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-edit/snapshots/add-group-with-fields/DataProcessors/Группа/Forms/Форма/Ext/Form.xml @@ -60,7 +60,7 @@ - cfg:ExternalDataProcessorObject.Группа + cfg:DataProcessorObject.Группа true diff --git a/tests/skills/cases/meta-compile/calculation-register.json b/tests/skills/cases/meta-compile/calculation-register.json index 30dae748..bc7b37ad 100644 --- a/tests/skills/cases/meta-compile/calculation-register.json +++ b/tests/skills/cases/meta-compile/calculation-register.json @@ -5,8 +5,6 @@ "name": "Начисления", "chartOfCalculationTypes": "ChartOfCalculationTypes.ВидыНачислений", "periodicity": "Month", - "actionPeriod": true, - "basePeriod": true, "dimensions": ["Сотрудник: CatalogRef.Сотрудники"], "resources": ["Результат: Number(15,2)", "ОтработаноДней: Number(5,0)"] }, diff --git a/tests/skills/cases/meta-compile/chart-of-accounts.json b/tests/skills/cases/meta-compile/chart-of-accounts.json index b835e0f5..6d7d8dff 100644 --- a/tests/skills/cases/meta-compile/chart-of-accounts.json +++ b/tests/skills/cases/meta-compile/chart-of-accounts.json @@ -5,9 +5,8 @@ "name": "Хозрасчетный", "codeLength": 4, "descriptionLength": 120, - "maxExtDimensionCount": 3, - "accountingFlags": ["Валютный", "Количественный"], - "extDimensionAccountingFlags": ["СуммовойУчет"] + "maxExtDimensionCount": 0, + "accountingFlags": ["Валютный", "Количественный"] }, "validatePath": "ChartsOfAccounts/Хозрасчетный", "expect": { diff --git a/tests/skills/cases/meta-compile/document-multiple-tabparts.json b/tests/skills/cases/meta-compile/document-multiple-tabparts.json index ae804b92..81da00d0 100644 --- a/tests/skills/cases/meta-compile/document-multiple-tabparts.json +++ b/tests/skills/cases/meta-compile/document-multiple-tabparts.json @@ -3,7 +3,6 @@ "input": { "type": "Document", "name": "РеализацияТоваров", - "registerRecords": ["AccumulationRegister.Продажи"], "attributes": [ "Организация: CatalogRef.Организации", "Контрагент: CatalogRef.Контрагенты", diff --git a/tests/skills/cases/meta-compile/snapshots/calculation-register/CalculationRegisters/Начисления.xml b/tests/skills/cases/meta-compile/snapshots/calculation-register/CalculationRegisters/Начисления.xml index af1347b2..cee46588 100644 --- a/tests/skills/cases/meta-compile/snapshots/calculation-register/CalculationRegisters/Начисления.xml +++ b/tests/skills/cases/meta-compile/snapshots/calculation-register/CalculationRegisters/Начисления.xml @@ -45,8 +45,8 @@ ChartOfCalculationTypes.ВидыНачислений Month - true - true + false + false diff --git a/tests/skills/cases/meta-compile/snapshots/chart-of-accounts/ChartsOfAccounts/Хозрасчетный.xml b/tests/skills/cases/meta-compile/snapshots/chart-of-accounts/ChartsOfAccounts/Хозрасчетный.xml index be8a1acb..6b826fc2 100644 --- a/tests/skills/cases/meta-compile/snapshots/chart-of-accounts/ChartsOfAccounts/Хозрасчетный.xml +++ b/tests/skills/cases/meta-compile/snapshots/chart-of-accounts/ChartsOfAccounts/Хозрасчетный.xml @@ -42,7 +42,7 @@ true - 3 + 0 4 120 @@ -522,38 +522,6 @@ Auto - - - СуммовойУчет - - - ru - Суммовой учет - - - - - xs:boolean - - false - - - - false - - false - false - - - DontCheck - - - Auto - - - Auto - - diff --git a/tests/skills/cases/meta-compile/snapshots/document-multiple-tabparts/Documents/РеализацияТоваров.xml b/tests/skills/cases/meta-compile/snapshots/document-multiple-tabparts/Documents/РеализацияТоваров.xml index fd3986a8..07d01121 100644 --- a/tests/skills/cases/meta-compile/snapshots/document-multiple-tabparts/Documents/РеализацияТоваров.xml +++ b/tests/skills/cases/meta-compile/snapshots/document-multiple-tabparts/Documents/РеализацияТоваров.xml @@ -192,9 +192,7 @@ AutoDelete WriteModified AutoFill - - AccumulationRegister.Продажи - + true true false diff --git a/tests/skills/cases/role-compile/explicit-rights.json b/tests/skills/cases/role-compile/explicit-rights.json index 60b67ebb..71483723 100644 --- a/tests/skills/cases/role-compile/explicit-rights.json +++ b/tests/skills/cases/role-compile/explicit-rights.json @@ -5,7 +5,9 @@ "script": "meta-compile/scripts/meta-compile", "input": { "type": "InformationRegister", - "name": "Цены" + "name": "Цены", + "dimensions": ["Номенклатура: String(100)"], + "resources": ["Цена: Number(15,2)"] }, "args": { "-JsonPath": "{inputFile}", diff --git a/tests/skills/cases/role-compile/snapshots/explicit-rights/InformationRegisters/Цены.xml b/tests/skills/cases/role-compile/snapshots/explicit-rights/InformationRegisters/Цены.xml index cc5c147c..9abe7a85 100644 --- a/tests/skills/cases/role-compile/snapshots/explicit-rights/InformationRegisters/Цены.xml +++ b/tests/skills/cases/role-compile/snapshots/explicit-rights/InformationRegisters/Цены.xml @@ -169,6 +169,97 @@ false false - + + + + Цена + + + ru + Цена + + + + + xs:decimal + + 15 + 2 + Any + + + false + + + + false + + false + false + + + false + + DontCheck + Items + + + Auto + Auto + + + Auto + DontIndex + Use + Use + + + + + Номенклатура + + + ru + Номенклатура + + + + + xs:string + + 100 + Variable + + + false + + + + false + + false + false + + + false + + DontCheck + Items + + + Auto + Auto + + + Auto + false + false + false + DontIndex + Use + Use + + + From 3ba6072660ac4c4da7f3e0eb14d11340e2c7a8f4 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Apr 2026 20:11:47 +0300 Subject: [PATCH 06/94] fix(meta-compile): strip FillFromFillingValue/FillValue/DataHistory for Chart* attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes attributes don't support FillFromFillingValue, FillValue, DataHistory properties — platform rejects them with "Неверное свойство объекта метаданных". Add "chart" context to Emit-Attribute to skip these. Found via platform snapshot verification (Finding A1). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../skills/meta-compile/scripts/meta-compile.ps1 | 16 ++++++++++------ .../skills/meta-compile/scripts/meta-compile.py | 12 ++++++++---- .../ChartsOfCalculationTypes/ВидыНачислений.xml | 3 --- .../ДополнительныеРеквизитыИСведения.xml | 3 --- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1 index 4f3a6ed4..19615a16 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.ps1 +++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1 @@ -1,4 +1,4 @@ -# meta-compile v1.5 — Compile 1C metadata object from JSON +# meta-compile v1.6 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -789,13 +789,13 @@ function Emit-Attribute { X "$indent`t`t" X "$indent`t`t" - # FillFromFillingValue — not for tabular/processor (non-stored objects don't have these) - if ($context -notin @("tabular", "processor")) { + # FillFromFillingValue — not for tabular/processor/chart (Chart* types don't support these) + if ($context -notin @("tabular", "processor", "chart")) { X "$indent`t`tfalse" } - # FillValue — not for tabular/processor - if ($context -notin @("tabular", "processor")) { + # FillValue — not for tabular/processor/chart + if ($context -notin @("tabular", "processor", "chart")) { Emit-FillValue "$indent`t`t" $typeStr } @@ -828,7 +828,10 @@ function Emit-Attribute { X "$indent`t`t$indexing" X "$indent`t`tUse" - X "$indent`t`tUse" + # DataHistory — not for Chart* types (ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes) + if ($context -ne "chart") { + X "$indent`t`tUse" + } } X "$indent`t" @@ -2633,6 +2636,7 @@ if ($objType -in $typesWithAttrTS) { "Catalog" { "catalog" } "Document" { "document" } { $_ -in @("DataProcessor","Report") } { "processor" } + { $_ -in @("ChartOfAccounts","ChartOfCharacteristicTypes","ChartOfCalculationTypes") } { "chart" } default { "object" } } foreach ($a in $attrs) { diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py index 3adbb1b9..30068532 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.py +++ b/.claude/skills/meta-compile/scripts/meta-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-compile v1.5 — Compile 1C metadata object from JSON +# meta-compile v1.6 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -747,9 +747,9 @@ def emit_attribute(indent, parsed, context): X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') - if context not in ('tabular', 'processor'): + if context not in ('tabular', 'processor', 'chart'): X(f'{indent}\t\tfalse') - if context not in ('tabular', 'processor'): + if context not in ('tabular', 'processor', 'chart'): emit_fill_value(f'{indent}\t\t', type_str) fill_checking = 'DontCheck' if 'req' in parsed.get('flags', []): @@ -777,7 +777,9 @@ def emit_attribute(indent, parsed, context): indexing = parsed['indexing'] X(f'{indent}\t\t{indexing}') X(f'{indent}\t\tUse') - X(f'{indent}\t\tUse') + # DataHistory — not for Chart* types (ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes) + if context != 'chart': + X(f'{indent}\t\tUse') X(f'{indent}\t') X(f'{indent}') @@ -2305,6 +2307,8 @@ if obj_type in types_with_attr_ts: context = 'document' elif obj_type in ('DataProcessor', 'Report'): context = 'processor' + elif obj_type in ('ChartOfAccounts', 'ChartOfCharacteristicTypes', 'ChartOfCalculationTypes'): + context = 'chart' else: context = 'object' for a in attrs: diff --git a/tests/skills/cases/meta-compile/snapshots/chart-of-calculation-types/ChartsOfCalculationTypes/ВидыНачислений.xml b/tests/skills/cases/meta-compile/snapshots/chart-of-calculation-types/ChartsOfCalculationTypes/ВидыНачислений.xml index 1e8b05e6..ebc76ee0 100644 --- a/tests/skills/cases/meta-compile/snapshots/chart-of-calculation-types/ChartsOfCalculationTypes/ВидыНачислений.xml +++ b/tests/skills/cases/meta-compile/snapshots/chart-of-calculation-types/ChartsOfCalculationTypes/ВидыНачислений.xml @@ -308,8 +308,6 @@ false - false - DontCheck Items @@ -321,7 +319,6 @@ Auto DontIndex Use - Use diff --git a/tests/skills/cases/meta-compile/snapshots/chart-of-characteristic-types/ChartsOfCharacteristicTypes/ДополнительныеРеквизитыИСведения.xml b/tests/skills/cases/meta-compile/snapshots/chart-of-characteristic-types/ChartsOfCharacteristicTypes/ДополнительныеРеквизитыИСведения.xml index ad6aa150..da7389fe 100644 --- a/tests/skills/cases/meta-compile/snapshots/chart-of-characteristic-types/ChartsOfCharacteristicTypes/ДополнительныеРеквизитыИСведения.xml +++ b/tests/skills/cases/meta-compile/snapshots/chart-of-characteristic-types/ChartsOfCharacteristicTypes/ДополнительныеРеквизитыИСведения.xml @@ -340,8 +340,6 @@ false - false - DontCheck Items @@ -353,7 +351,6 @@ Auto DontIndex Use - Use From 358830c65bf2e1f98d757d5f921326ac8790ddf8 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Apr 2026 20:24:08 +0300 Subject: [PATCH 07/94] =?UTF-8?q?feat(meta-validate):=20Check=2010=20?= =?UTF-8?q?=E2=80=94=20warn=20on=20empty=20registers=20and=20broken=20Regi?= =?UTF-8?q?sterRecords=20refs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two new validations found via platform snapshot verification: - Registers without any Dimensions/Resources/Attributes → platform rejects - Document.RegisterRecords referencing non-existent register objects Co-Authored-By: Claude Opus 4.6 (1M context) --- .../meta-validate/scripts/meta-validate.ps1 | 56 ++++++++++++++++++- .../meta-validate/scripts/meta-validate.py | 48 +++++++++++++++- 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/.claude/skills/meta-validate/scripts/meta-validate.ps1 b/.claude/skills/meta-validate/scripts/meta-validate.ps1 index d30f210a..732d7670 100644 --- a/.claude/skills/meta-validate/scripts/meta-validate.ps1 +++ b/.claude/skills/meta-validate/scripts/meta-validate.ps1 @@ -1,4 +1,4 @@ -# meta-validate v1.2 — Validate 1C metadata object structure +# meta-validate v1.3 — Validate 1C metadata object structure # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -998,6 +998,18 @@ if ($propsNode) { } } + # CalculationRegister: ActionPeriod=true requires non-empty Schedule + if ($mdType -eq "CalculationRegister") { + $actionPeriod = $propsNode.SelectSingleNode("md:ActionPeriod", $ns) + if ($actionPeriod -and $actionPeriod.InnerText -eq "true") { + $schedule = $propsNode.SelectSingleNode("md:Schedule", $ns) + if (-not $schedule -or -not $schedule.InnerText.Trim()) { + Report-Warn "10. CalculationRegister: ActionPeriod=true but Schedule is empty — platform requires a schedule register" + $check10Issues++ + } + } + } + # DocumentJournal: RegisteredDocuments should not be empty if ($mdType -eq "DocumentJournal") { $regDocs = $propsNode.SelectSingleNode("md:RegisteredDocuments", $ns) @@ -1025,6 +1037,48 @@ if ($propsNode) { } } + # Register: must have at least one Dimension or Resource (platform rejects empty registers) + $regTypesAll = @("AccumulationRegister","AccountingRegister","CalculationRegister","InformationRegister") + if ($regTypesAll -contains $mdType -and $childObjNode) { + $dims = $childObjNode.SelectNodes("md:Dimension", $ns).Count + $ress = $childObjNode.SelectNodes("md:Resource", $ns).Count + $attrs = $childObjNode.SelectNodes("md:Attribute", $ns).Count + if (($dims + $ress + $attrs) -eq 0) { + Report-Warn "10. $mdType`: no Dimensions, Resources, or Attributes — platform will reject" + $check10Issues++ + } + } + + # Document: RegisterRecords references should point to existing objects in config + if ($mdType -eq "Document" -and $script:configDir) { + $regRecords = $propsNode.SelectSingleNode("md:RegisterRecords", $ns) + if ($regRecords) { + $items = $regRecords.SelectNodes("xr:Item", $ns) + foreach ($item in $items) { + $refVal = $item.InnerText.Trim() + if (-not $refVal) { continue } + # Parse "AccumulationRegister.Name" → dir AccumulationRegisters/Name + $parts = $refVal -split '\.',2 + if ($parts.Count -eq 2) { + $refType = $parts[0]; $refName = $parts[1] + $dirMap = @{ + "AccumulationRegister"="AccumulationRegisters"; "InformationRegister"="InformationRegisters" + "AccountingRegister"="AccountingRegisters"; "CalculationRegister"="CalculationRegisters" + } + $refDir = $dirMap[$refType] + if ($refDir) { + $refPath = Join-Path $script:configDir "$refDir/$refName" + $refXml = Join-Path $script:configDir "$refDir/$refName.xml" + if (-not (Test-Path $refPath) -and -not (Test-Path $refXml)) { + Report-Warn "10. Document.RegisterRecords references '$refVal' but object not found in config" + $check10Issues++ + } + } + } + } + } + } + # Register: must have at least one registrar document $registerTypes = @("AccumulationRegister","AccountingRegister","CalculationRegister","InformationRegister") if ($registerTypes -contains $mdType -and $script:configDir -and $objName -ne "(unknown)") { diff --git a/.claude/skills/meta-validate/scripts/meta-validate.py b/.claude/skills/meta-validate/scripts/meta-validate.py index c55ef8ae..45b92a38 100644 --- a/.claude/skills/meta-validate/scripts/meta-validate.py +++ b/.claude/skills/meta-validate/scripts/meta-validate.py @@ -1,4 +1,4 @@ -# meta-validate v1.2 — Validate 1C metadata object structure (Python port) +# meta-validate v1.3 — Validate 1C metadata object structure (Python port) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os @@ -942,6 +942,15 @@ if props_node is not None: check10_issues += 1 print('[HINT] /meta-edit -Operation modify-property -Value "Task=Task.XXX"') + # CalculationRegister: ActionPeriod=true requires non-empty Schedule + if md_type == 'CalculationRegister': + action_period = find(props_node, 'md:ActionPeriod') + if action_period is not None and text_of(action_period) == 'true': + schedule = find(props_node, 'md:Schedule') + if schedule is None or not text_of(schedule): + report_warn('10. CalculationRegister: ActionPeriod=true but Schedule is empty — platform requires a schedule register') + check10_issues += 1 + # DocumentJournal: RegisteredDocuments should not be empty if md_type == 'DocumentJournal': reg_docs = find(props_node, 'md:RegisteredDocuments') @@ -969,6 +978,43 @@ if props_node is not None: check10_issues += 1 print('[HINT] /meta-edit -Operation modify-property -Value "ExtDimensionTypes=ChartOfCharacteristicTypes.XXX"') + # Register: must have at least one Dimension or Resource (platform rejects empty registers) + reg_types_all = ('AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'InformationRegister') + if md_type in reg_types_all and child_obj_node is not None: + dims = len(find_all(child_obj_node, 'md:Dimension')) + ress = len(find_all(child_obj_node, 'md:Resource')) + attrs = len(find_all(child_obj_node, 'md:Attribute')) + if dims + ress + attrs == 0: + report_warn(f"10. {md_type}: no Dimensions, Resources, or Attributes \u2014 platform will reject") + check10_issues += 1 + + # Document: RegisterRecords references should point to existing objects in config + if md_type == 'Document' and config_dir: + reg_records = find(props_node, 'md:RegisterRecords') + if reg_records is not None: + rr_items = find_all(reg_records, 'xr:Item') + for item in rr_items: + ref_val = (inner_text(item) or '').strip() + if not ref_val: + continue + # Parse "AccumulationRegister.Name" -> dir AccumulationRegisters/Name + parts = ref_val.split('.', 1) + if len(parts) == 2: + ref_type, ref_name = parts + dir_map = { + 'AccumulationRegister': 'AccumulationRegisters', + 'InformationRegister': 'InformationRegisters', + 'AccountingRegister': 'AccountingRegisters', + 'CalculationRegister': 'CalculationRegisters', + } + ref_dir = dir_map.get(ref_type) + if ref_dir: + ref_path = os.path.join(config_dir, ref_dir, ref_name) + ref_xml = os.path.join(config_dir, ref_dir, ref_name + '.xml') + if not os.path.exists(ref_path) and not os.path.exists(ref_xml): + report_warn(f"10. Document.RegisterRecords references '{ref_val}' but object not found in config") + check10_issues += 1 + # Register: must have at least one registrar document register_types = ('AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'InformationRegister') if md_type in register_types and config_dir and obj_name != '(unknown)': From 1bc5e8f07a69d9fb65b261982b1d54d66c42341e Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 6 Apr 2026 14:17:33 +0300 Subject: [PATCH 08/94] feat(skd-compile): hidden, valueListAllowed, drilldown, groupHeaderTemplate, dataPath fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: calculatedField dataPath fallback from "field" key (#5) - fix: groupHeaderTemplate vs groupTemplate, groupName support (#7) - feat: @valueList / valueListAllowed for parameters (#4) - feat: @hidden / hidden params + "dataParameters": "auto" (#1) - feat: drilldown in template params — DetailsAreaTemplateParameter + appearance (#2+#3) - fix(template-add): improved error message with path hint (#6) - docs: SKILL.md updated with new keys and examples - version bump: skd-compile v1.4, template-add v1.2 Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/skd-compile/SKILL.md | 30 ++++- .../skd-compile/scripts/skd-compile.ps1 | 118 ++++++++++++++++-- .../skills/skd-compile/scripts/skd-compile.py | 117 +++++++++++++++-- .../template-add/scripts/add-template.ps1 | 4 +- .../template-add/scripts/add-template.py | 6 +- 5 files changed, 248 insertions(+), 27 deletions(-) diff --git a/.claude/skills/skd-compile/SKILL.md b/.claude/skills/skd-compile/SKILL.md index afa3beaa..2d535adb 100644 --- a/.claude/skills/skd-compile/SKILL.md +++ b/.claude/skills/skd-compile/SKILL.md @@ -97,7 +97,14 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p ] ``` -`@autoDates` — автоматически генерирует параметры `ДатаНачала` и `ДатаОкончания` с выражениями `&Период.ДатаНачала` / `&Период.ДатаОкончания` и `availableAsField=false`. Заменяет 5 строк на 1. +Флаги shorthand: +- `@autoDates` — автоматически генерирует параметры `ДатаНачала` и `ДатаОкончания` с выражениями `&Период.ДатаНачала` / `&Период.ДатаОкончания` и `availableAsField=false` +- `@valueList` — `true` — разрешает передавать список значений +- `@hidden` — скрытый параметр: `availableAsField=false` + исключается из `"dataParameters": "auto"` + +Объектная форма: `hidden: true`, `valueListAllowed: true`, `availableAsField: false`. + +В варианте настроек `"dataParameters": "auto"` автоматически генерирует записи для всех не-hidden параметров с `userSettingID`. ### Фильтры — shorthand @@ -235,15 +242,32 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p Raw XML (`"template": "<...>"`) остаётся как fallback. Детект: если есть `rows` — DSL, иначе — raw. +### Расшифровка (drilldown) в параметрах шаблона + +Ключ `drilldown` в параметре шаблона автоматически генерирует `DetailsAreaTemplateParameter` и привязку `Расшифровка` в appearance ячеек: + +```json +"parameters": [ + { "name": "Сырье", "expression": "ПоступлениеСырья", "drilldown": "ПоступлениеСырья" } +] +``` + +Генерирует: `ExpressionAreaTemplateParameter` (обычный) + `DetailsAreaTemplateParameter` с именем `Расшифровка_ПоступлениеСырья`, `fieldExpression` по полю `ИмяРесурса`, `mainAction=DrillDown`. Ячейки `{Сырье}` автоматически получают appearance `Расшифровка = Расшифровка_ПоступлениеСырья`. + ### Привязки макетов к группировкам ```json "groupTemplates": [ - { "groupField": "Счет", "templateType": "GroupHeader", "template": "Макет1" }, - { "groupField": "Счет", "templateType": "Header", "template": "Макет2" } + { "groupName": "ДанныеОтчета", "templateType": "GroupHeader", "template": "Макет1" }, + { "groupField": "Счет", "templateType": "Header", "template": "Макет2" }, + { "groupField": "Счет", "templateType": "OverallHeader", "template": "Макет3" } ] ``` +`groupField` — привязка к полю группировки, `groupName` — к именованной группировке в структуре варианта. + +`templateType`: `Header` (строки данных) → ``, `OverallHeader` (итоги) → ``, `GroupHeader` (шапка) → ``. + ## Примеры ### Минимальный diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1 index 528c5916..b89ec461 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.ps1 +++ b/.claude/skills/skd-compile/scripts/skd-compile.ps1 @@ -1,4 +1,4 @@ -# skd-compile v1.3 — Compile 1C DCS from JSON +# skd-compile v1.4 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -322,6 +322,18 @@ function Parse-ParamShorthand { $s = $s -replace '\s*@autoDates', '' } + # Extract @valueList flag + if ($s -match '@valueList') { + $result.valueListAllowed = $true + $s = $s -replace '\s*@valueList', '' + } + + # Extract @hidden flag + if ($s -match '@hidden') { + $result.hidden = $true + $s = $s -replace '\s*@hidden', '' + } + # Split "Name: Type = Value" if ($s -match '^([^:]+):\s*(\S+)(\s*=\s*(.+))?$') { $result.name = $Matches[1].Trim() @@ -746,8 +758,9 @@ function Emit-CalcFields { if ($cf -is [string]) { $parsed = Parse-CalcShorthand $cf } else { + $dp = if ($cf.dataPath) { "$($cf.dataPath)" } else { "$($cf.field)" } $parsed = @{ - dataPath = "$($cf.dataPath)" + dataPath = $dp expression = "$($cf.expression)" } } @@ -854,11 +867,19 @@ function Emit-SingleParam { X "`t`t$(Esc-Xml $parsed.expression)" } + # Hidden implies availableAsField=false + if ($parsed.hidden -eq $true) { $parsed.availableAsField = $false } + # AvailableAsField if ($parsed.availableAsField -eq $false) { X "`t`tfalse" } + # ValueListAllowed + if ($parsed.valueListAllowed -eq $true) { + X "`t`ttrue" + } + # Use if ($p -isnot [string] -and $p.use) { X "`t`t$(Esc-Xml "$($p.use)")" @@ -867,6 +888,8 @@ function Emit-SingleParam { X "`t" } +$script:allParams = @() + function Emit-Parameters { if (-not $def.parameters) { return } foreach ($p in $def.parameters) { @@ -881,11 +904,16 @@ function Emit-Parameters { } if ($p.expression) { $parsed.expression = "$($p.expression)" } if ($p.availableAsField -eq $false) { $parsed.availableAsField = $false } + if ($p.valueListAllowed -eq $true) { $parsed.valueListAllowed = $true } + if ($p.hidden -eq $true) { $parsed.hidden = $true } if ($p.autoDates -eq $true) { $parsed.autoDates = $true } } Emit-SingleParam -p $p -parsed $parsed + # Track parameter for auto dataParameters + $script:allParams += @{ name = $parsed.name; hidden = [bool]$parsed.hidden; type = "$($parsed.type)"; value = $parsed.value } + # @autoDates: auto-generate ДатаНачала and ДатаОкончания if ($parsed.autoDates) { $paramName = $parsed.name @@ -1005,7 +1033,7 @@ function Emit-ColorValue { } function Emit-CellAppearance { - param($style, [double]$width = 0, [bool]$vMerge = $false, [double]$minHeight = 0) + param($style, [double]$width = 0, [bool]$vMerge = $false, [double]$minHeight = 0, $extraItems = @()) $ind = "`t`t`t`t`t" X "`t`t`t`t" # Background color @@ -1098,6 +1126,8 @@ function Emit-CellAppearance { X "$ind`ttrue" X "$ind" } + # Extra appearance items (e.g. drilldown Расшифровка) + foreach ($ei in $extraItems) { X $ei } X "`t`t`t`t" } @@ -1128,6 +1158,14 @@ function Emit-AreaTemplateDSL { } if (-not $vMerge.ContainsKey(0)) { $vMerge[0] = @{} } + # Build drilldown map: param_name -> drilldown_value + $drilldownMap = @{} + if ($t.parameters) { + foreach ($tp in $t.parameters) { + if ($tp.drilldown) { $drilldownMap["$($tp.name)"] = "$($tp.drilldown)" } + } + } + X "`t" @@ -1222,11 +1296,20 @@ function Emit-Templates { function Emit-GroupTemplates { if (-not $def.groupTemplates) { return } foreach ($gt in $def.groupTemplates) { - X "`t" - X "`t`t$(Esc-Xml "$($gt.groupField)")" - X "`t`t$(Esc-Xml "$($gt.templateType)")" + $ttype = if ($gt.templateType) { "$($gt.templateType)" } else { "Header" } + $isHeader = ($ttype -eq 'GroupHeader') + $tag = if ($isHeader) { 'groupHeaderTemplate' } else { 'groupTemplate' } + $xmlTType = if ($isHeader) { 'Header' } else { $ttype } + + X "`t<$tag>" + if ($gt.groupName) { + X "`t`t$(Esc-Xml "$($gt.groupName)")" + } elseif ($gt.groupField) { + X "`t`t$(Esc-Xml "$($gt.groupField)")" + } + X "`t`t$(Esc-Xml $xmlTType)" X "`t`t" - X "`t" + X "`t" } } @@ -1868,7 +1951,22 @@ function Emit-SettingsVariants { } # DataParameters - if ($s.dataParameters) { + if ($s.dataParameters -eq 'auto') { + # Auto-generate dataParameters for all non-hidden params + $autoDP = @() + foreach ($ap in $script:allParams) { + if (-not $ap.hidden) { + $dpItem = New-Object PSObject + $dpItem | Add-Member -NotePropertyName "parameter" -NotePropertyValue $ap.name + $dpItem | Add-Member -NotePropertyName "use" -NotePropertyValue $false + $dpItem | Add-Member -NotePropertyName "userSettingID" -NotePropertyValue "auto" + $autoDP += $dpItem + } + } + if ($autoDP.Count -gt 0) { + Emit-DataParameters -items $autoDP -indent "`t`t`t" + } + } elseif ($s.dataParameters) { Emit-DataParameters -items $s.dataParameters -indent "`t`t`t" } diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py index 33198a0d..35a86414 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.py +++ b/.claude/skills/skd-compile/scripts/skd-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# skd-compile v1.3 — Compile 1C DCS from JSON +# skd-compile v1.4 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -233,6 +233,16 @@ def parse_param_shorthand(s): result['autoDates'] = True s = re.sub(r'\s*@autoDates', '', s) + # Extract @valueList flag + if '@valueList' in s: + result['valueListAllowed'] = True + s = re.sub(r'\s*@valueList', '', s) + + # Extract @hidden flag + if '@hidden' in s: + result['hidden'] = True + s = re.sub(r'\s*@hidden', '', s) + # Split "Name: Type = Value" m = re.match(r'^([^:]+):\s*(\S+)(\s*=\s*(.+))?$', s) if m: @@ -598,7 +608,7 @@ def emit_calc_fields(lines, defn): is_obj = False else: parsed = { - 'dataPath': str(cf.get('dataPath', '')), + 'dataPath': str(cf.get('dataPath') or cf.get('field', '')), 'expression': str(cf.get('expression', '')), } is_obj = True @@ -724,10 +734,18 @@ def emit_single_param(lines, p, parsed): if parsed.get('expression'): lines.append(f'\t\t{esc_xml(parsed["expression"])}') + # Hidden implies availableAsField=false + if parsed.get('hidden'): + parsed['availableAsField'] = False + # AvailableAsField if parsed.get('availableAsField') is False: lines.append('\t\tfalse') + # ValueListAllowed + if parsed.get('valueListAllowed'): + lines.append('\t\ttrue') + # Use if p is not None and not isinstance(p, str) and p.get('use'): lines.append(f'\t\t{esc_xml(str(p["use"]))}') @@ -735,7 +753,12 @@ def emit_single_param(lines, p, parsed): lines.append('\t') +_all_params = [] + + def emit_parameters(lines, defn): + global _all_params + _all_params = [] if not defn.get('parameters'): return for p in defn['parameters']: @@ -752,11 +775,23 @@ def emit_parameters(lines, defn): parsed['expression'] = str(p['expression']) if p.get('availableAsField') is False: parsed['availableAsField'] = False + if p.get('valueListAllowed') is True: + parsed['valueListAllowed'] = True + if p.get('hidden') is True: + parsed['hidden'] = True if p.get('autoDates') is True: parsed['autoDates'] = True emit_single_param(lines, p, parsed) + # Track parameter for auto dataParameters + _all_params.append({ + 'name': parsed['name'], + 'hidden': bool(parsed.get('hidden')), + 'type': parsed.get('type', ''), + 'value': parsed.get('value'), + }) + # @autoDates: auto-generate ДатаНачала and ДатаОкончания if parsed.get('autoDates'): param_name = parsed['name'] @@ -827,7 +862,7 @@ def _emit_color_value(lines, color, indent): lines.append(f'{indent}{esc_xml(color)}') -def _emit_cell_appearance(lines, style, width=0, v_merge=False, min_height=0): +def _emit_cell_appearance(lines, style, width=0, v_merge=False, min_height=0, extra_items=None): ind = '\t\t\t\t\t' lines.append('\t\t\t\t') # Background color @@ -909,6 +944,10 @@ def _emit_cell_appearance(lines, style, width=0, v_merge=False, min_height=0): lines.append(f'{ind}\t\u041e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c\u041f\u043e\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438') lines.append(f'{ind}\ttrue') lines.append(f'{ind}') + # Extra appearance items (e.g. drilldown) + if extra_items: + for ei in extra_items: + lines.append(ei) lines.append('\t\t\t\t') @@ -935,6 +974,13 @@ def _emit_area_template_dsl(lines, t): if 0 not in v_merge: v_merge[0] = {} + # Build drilldown map: param_name -> drilldown_value + drilldown_map = {} + if t.get('parameters'): + for tp in t['parameters']: + if tp.get('drilldown'): + drilldown_map[str(tp['name'])] = str(tp['drilldown']) + lines.append('\t') @@ -1016,11 +1093,19 @@ def emit_group_templates(lines, defn): if not defn.get('groupTemplates'): return for gt in defn['groupTemplates']: - lines.append('\t') - lines.append(f'\t\t{esc_xml(str(gt["groupField"]))}') - lines.append(f'\t\t{esc_xml(str(gt["templateType"]))}') + ttype = str(gt.get('templateType', '')) or 'Header' + is_header = (ttype == 'GroupHeader') + tag = 'groupHeaderTemplate' if is_header else 'groupTemplate' + xml_ttype = 'Header' if is_header else ttype + + lines.append(f'\t<{tag}>') + if gt.get('groupName'): + lines.append(f'\t\t{esc_xml(str(gt["groupName"]))}') + elif gt.get('groupField'): + lines.append(f'\t\t{esc_xml(str(gt["groupField"]))}') + lines.append(f'\t\t{esc_xml(xml_ttype)}') lines.append(f'\t\t') - lines.append('\t') + lines.append(f'\t') # === Settings Variants === @@ -1540,7 +1625,19 @@ def emit_settings_variants(lines, defn): emit_output_parameters(lines, s['outputParameters'], '\t\t\t') # DataParameters - if s.get('dataParameters'): + if s.get('dataParameters') == 'auto': + # Auto-generate dataParameters for all non-hidden params + auto_dp = [] + for ap in _all_params: + if not ap['hidden']: + auto_dp.append({ + 'parameter': ap['name'], + 'use': False, + 'userSettingID': 'auto', + }) + if auto_dp: + emit_data_parameters(lines, auto_dp, '\t\t\t') + elif s.get('dataParameters'): emit_data_parameters(lines, s['dataParameters'], '\t\t\t') # Structure (supports string shorthand) diff --git a/.claude/skills/template-add/scripts/add-template.ps1 b/.claude/skills/template-add/scripts/add-template.ps1 index 614403e1..c6253e5b 100644 --- a/.claude/skills/template-add/scripts/add-template.ps1 +++ b/.claude/skills/template-add/scripts/add-template.ps1 @@ -1,4 +1,4 @@ -# template-add v1.1 — Add template to 1C object +# template-add v1.2 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -37,7 +37,7 @@ $tmpl = $typeMap[$TemplateType] $rootXmlPath = Join-Path $SrcDir "$ObjectName.xml" if (-not (Test-Path $rootXmlPath)) { - Write-Error "Корневой файл обработки не найден: $rootXmlPath" + Write-Error "Корневой файл объекта не найден: $rootXmlPath`nОжидается: //.xml`nПодсказка: SrcDir должен указывать на папку типа объектов (например Reports), а не на корень конфигурации" exit 1 } diff --git a/.claude/skills/template-add/scripts/add-template.py b/.claude/skills/template-add/scripts/add-template.py index 096981cf..8a2f61d2 100644 --- a/.claude/skills/template-add/scripts/add-template.py +++ b/.claude/skills/template-add/scripts/add-template.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# add-template v1.0 — Add template to 1C object +# add-template v1.2 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -63,7 +63,9 @@ def main(): root_xml_path = os.path.join(src_dir, f"{object_name}.xml") if not os.path.exists(root_xml_path): - print(f"Корневой файл обработки не найден: {root_xml_path}", file=sys.stderr) + print(f"Корневой файл объекта не найден: {root_xml_path}", file=sys.stderr) + print(f"Ожидается: //.xml", file=sys.stderr) + print(f"Подсказка: SrcDir должен указывать на папку типа объектов (например Reports), а не на корень конфигурации", file=sys.stderr) sys.exit(1) processor_dir = os.path.join(src_dir, object_name) From 08688f5cab89d556eabbf71e8c1fd7563c568c90 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 6 Apr 2026 14:36:04 +0300 Subject: [PATCH 09/94] docs(skd): update specs for hidden, valueListAllowed, drilldown, groupHeaderTemplate - skd-dsl-spec: @valueList, @hidden, field alias, dataParameters auto, drilldown, groupName/GroupHeader - skd-guide: new parameter flags, dataParameters auto, groupName, drilldown - 1c-dcs-spec: valueListAllowed element, DetailsAreaTemplateParameter, groupHeaderTemplate Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/1c-dcs-spec.md | 50 ++++++++++++++++++++++++++++++++------ docs/skd-dsl-spec.md | 58 +++++++++++++++++++++++++++++++++++++++++--- docs/skd-guide.md | 10 +++++--- 3 files changed, 105 insertions(+), 13 deletions(-) diff --git a/docs/1c-dcs-spec.md b/docs/1c-dcs-spec.md index cb0ca398..1dbe89a2 100644 --- a/docs/1c-dcs-spec.md +++ b/docs/1c-dcs-spec.md @@ -515,6 +515,7 @@ DataCompositionSchema | `useRestriction` | нет | `true` — параметр скрыт от пользователя, `false` — доступен | | `expression` | нет | Выражение для автоматического вычисления (например, `&Период.ДатаНачала`) | | `availableAsField` | нет | `false` — параметр недоступен как поле в отчёте | +| `valueListAllowed` | нет | `true` — разрешает передавать список значений в параметр | | `use` | нет | Режим: `Always` (всегда), `Auto` (автоматически) | ### Типы значений параметров @@ -562,26 +563,61 @@ DataCompositionSchema |---|---| | `name` | Имя макета (ссылаются groupTemplate) | | `template` (вложенный) | Описание строк/ячеек (`dcsat:AreaTemplate`) | -| `parameter` | Параметры макета (`dcsat:ExpressionAreaTemplateParameter`) — выражения для подстановки | +| `parameter` (Expression) | Параметры макета (`dcsat:ExpressionAreaTemplateParameter`) — выражения для подстановки | +| `parameter` (Details) | Параметры расшифровки (`dcsat:DetailsAreaTemplateParameter`) — для drilldown | + +#### DetailsAreaTemplateParameter + +Параметр расшифровки — активирует drilldown при клике на ячейку: + +```xml + + Расшифровка_ПоступлениеСырья + + ИмяРесурса + "ПоступлениеСырья" + + DrillDown + +``` + +Привязка к ячейке — через appearance `Расшифровка`: + +```xml + + Расшифровка + Расшифровка_ПоступлениеСырья + +``` --- -## 10. Привязки макетов группировок (groupTemplate) +## 10. Привязки макетов группировок (groupTemplate, groupHeaderTemplate) -Связывают группировку с пользовательским макетом: +Связывают группировку с пользовательским макетом. Два XML-элемента: + +- `` — шаблон строки данных (`Header`) и итогов (`OverallHeader`) +- `` — шаблон заголовка группировки (шапка таблицы) ```xml - - ТипЦен + + ДанныеОтчета Header + + + Счет + Header + ``` | Элемент | Описание | |---|---| -| `groupField` | Имя поля группировки | -| `templateType` | Тип: `Header` (заголовок), `Footer` (подвал), `Overall` (общий) | +| `groupField` | Привязка к полю группировки | +| `groupName` | Привязка к именованной группировке в структуре варианта | +| `templateType` | `Header` (строки данных), `OverallHeader` (итоги) | | `template` | Ссылка на имя template из раздела 9 | --- diff --git a/docs/skd-dsl-spec.md b/docs/skd-dsl-spec.md index a3dcb43f..1d9f1467 100644 --- a/docs/skd-dsl-spec.md +++ b/docs/skd-dsl-spec.md @@ -297,7 +297,7 @@ XML-маппинг — по `` на каждый элемент: ### Shorthand ``` -": [= ] [@autoDates]" +": [= ] [@autoDates] [@valueList] [@hidden]" ``` Примеры: @@ -332,6 +332,25 @@ XML-маппинг — по `` на каждый элемент: "parameters": ["Период: StandardPeriod = LastMonth @autoDates"] ``` +### @valueList + +Флаг `@valueList` генерирует `true` — разрешает передавать список значений в параметр: + +```json +"parameters": ["Организации: CatalogRef.Организации @valueList"] +``` + +### @hidden + +Флаг `@hidden` — скрытый параметр. Автоматически ставит `availableAsField=false` и исключает параметр из автогенерируемых `dataParameters` при `"dataParameters": "auto"`: + +```json +"parameters": [ + { "name": "Счет43", "type": "ChartOfAccountsRef.Хозрасчетный", "value": "...", "hidden": true }, + "СкрытыйПараметр: string = test @hidden" +] +``` + ### Объектная форма ```json @@ -355,6 +374,8 @@ XML-маппинг — по `` на каждый элемент: | `value` | Значение по умолчанию | | `expression` | Выражение для вычисления | | `availableAsField` | `false` — скрыть из полей | +| `valueListAllowed` | `true` — разрешить список значений | +| `hidden` | `true` — скрытый параметр (авто `availableAsField=false`, исключение из `dataParameters: auto`) | | `useRestriction` | `true` — скрыть от пользователя | | `use` | `"Always"`, `"Auto"` | @@ -399,6 +420,8 @@ XML-маппинг — по `` на каждый элемент: } ``` +Ключ `field` — алиас для `dataPath` (используется если `dataPath` не указан). + --- ## 8. Связи наборов (dataSetLinks) @@ -606,6 +629,14 @@ XML-маппинг — по `` на каждый элемент: ### dataParameters +#### Автогенерация + +```json +"dataParameters": "auto" +``` + +Генерирует записи `dataParameters` для всех не-hidden параметров с `userSettingID`. Скрытые параметры (`hidden: true` / `@hidden`) исключаются. + #### Shorthand-строка ```json @@ -760,7 +791,7 @@ XML-маппинг — по `` на каждый элемент: | `style` | Именованный пресет оформления (по умолчанию `"data"`) | | `widths` | Массив ширин колонок (применяется ко всем строкам) | | `minHeight` | Минимальная высота первой строки (для шапок) | -| `parameters` | Параметры макета — выражения для подстановки | +| `parameters` | Параметры макета — выражения для подстановки (поддерживают `drilldown`) | #### Синтаксис ячеек @@ -820,14 +851,35 @@ XML-маппинг — по `` на каждый элемент: Детект: если есть `rows` — используется компактный DSL, иначе — raw XML из `template`. +#### Расшифровка (drilldown) в параметрах шаблона + +Ключ `drilldown` в параметре шаблона автоматически генерирует: +1. `DetailsAreaTemplateParameter` с именем `Расшифровка_<значение>`, `fieldExpression` по полю `ИмяРесурса`, `mainAction=DrillDown` +2. Привязку `Расшифровка` в appearance ячеек, ссылающихся на этот параметр через `{Имя}` + +```json +"parameters": [ + { "name": "Сырье", "expression": "ПоступлениеСырья", "drilldown": "ПоступлениеСырья" } +] +``` + ### groupTemplates ```json "groupTemplates": [ - { "groupField": "ТипЦен", "templateType": "Header", "template": "Макет1" } + { "groupName": "ДанныеОтчета", "templateType": "GroupHeader", "template": "Макет1" }, + { "groupField": "Счет", "templateType": "Header", "template": "Макет2" }, + { "groupField": "Счет", "templateType": "OverallHeader", "template": "Макет3" } ] ``` +| Ключ | Описание | +|------|----------| +| `groupField` | Привязка к полю группировки → `` | +| `groupName` | Привязка к именованной группировке в структуре варианта → `` | +| `templateType` | `Header` / `OverallHeader` → ``, `GroupHeader` → `` | +| `template` | Имя макета | + --- ## 11. Полный пример — минимальный diff --git a/docs/skd-guide.md b/docs/skd-guide.md index 65cb07e2..cda86ddb 100644 --- a/docs/skd-guide.md +++ b/docs/skd-guide.md @@ -71,7 +71,7 @@ ] ``` -`@autoDates` автоматически генерирует параметры `ДатаНачала`/`ДатаОкончания` (заменяет 5 строк на 1). +Флаги: `@autoDates` (авто ДатаНачала/ДатаОкончания), `@valueList` (разрешить список значений), `@hidden` (скрыть параметр, исключить из `dataParameters: auto`). ### Вычисляемые поля — shorthand @@ -96,7 +96,7 @@ ``` - **filter shorthand**: `"Поле оператор значение @флаги"` — флаги `@off`, `@user`, `@quickAccess`, `@normal`, `@inaccessible` -- **dataParameters shorthand**: `"Имя = значение @флаги"` +- **dataParameters shorthand**: `"Имя = значение @флаги"`, или `"auto"` — автогенерация для всех не-hidden параметров - **structure shorthand**: `"Поле1 > Поле2 > details"` — `>` разделяет уровни группировки - **conditionalAppearance**: условное оформление с автоопределением типов значений (Color, Boolean, LocalStringType) @@ -125,13 +125,17 @@ } ], "groupTemplates": [ - { "groupField": "Счет", "templateType": "GroupHeader", "template": "Макет1" }, + { "groupName": "ДанныеОтчета", "templateType": "GroupHeader", "template": "Макет1" }, { "groupField": "Счет", "templateType": "Header", "template": "Макет2" } ] ``` Синтаксис ячеек: `"текст"` — статика, `"{Имя}"` — параметр, `"|"` — объединение с ячейкой выше, `null` — пустая. +Привязки: `groupField` — к полю, `groupName` — к именованной группировке. `templateType`: `Header`/`OverallHeader` → ``, `GroupHeader` → ``. + +Расшифровка (drilldown): ключ `drilldown` в параметре шаблона генерирует `DetailsAreaTemplateParameter` и привязку `Расшифровка` в appearance ячеек. + Встроенные стили: `header` (фон, центр, перенос), `data` (фон группы), `subheader` (без фона, центр), `total` (без фона). Все — Arial 10, рамки Solid 1px, цвета через стили платформы. Пользовательские стили — через `skd-styles.json` в директории проекта. ### Объектная форма From e4dcef8c905d6cdce9df677ffec87f9646a03845 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 6 Apr 2026 14:44:14 +0300 Subject: [PATCH 10/94] fix(skd-compile): DesignTimeValue, useRestriction for hidden, named structure groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: auto-detect DesignTimeValue for ПланСчетов/Справочник/Перечисление/Документ values (#9) - fix: hidden params auto-set useRestriction=true alongside availableAsField=false (#11) - feat: named groups in structure shorthand "ИмяГруппы[Поле] > details" (#10) - version bump: skd-compile v1.5 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../skd-compile/scripts/skd-compile.ps1 | 19 ++++++++++++----- .../skills/skd-compile/scripts/skd-compile.py | 21 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1 index b89ec461..d6626da5 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.ps1 +++ b/.claude/skills/skd-compile/scripts/skd-compile.ps1 @@ -1,4 +1,4 @@ -# skd-compile v1.4 — Compile 1C DCS from JSON +# skd-compile v1.5 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -857,8 +857,14 @@ function Emit-SingleParam { # Value Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t" + # Hidden implies useRestriction=true + availableAsField=false + if ($parsed.hidden -eq $true) { + $parsed.availableAsField = $false + $parsed.useRestriction = $true + } + # UseRestriction - if ($p -isnot [string] -and $p.useRestriction -eq $true) { + if ($parsed.useRestriction -eq $true -or ($p -isnot [string] -and $p.useRestriction -eq $true)) { X "`t`ttrue" } @@ -867,9 +873,6 @@ function Emit-SingleParam { X "`t`t$(Esc-Xml $parsed.expression)" } - # Hidden implies availableAsField=false - if ($parsed.hidden -eq $true) { $parsed.availableAsField = $false } - # AvailableAsField if ($parsed.availableAsField -eq $false) { X "`t`tfalse" @@ -957,6 +960,8 @@ function Emit-ParamValue { X "$indent$(Esc-Xml $valStr)" } elseif ($valStr -eq "true" -or $valStr -eq "false") { X "$indent$(Esc-Xml $valStr)" + } elseif ($valStr -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or $valStr -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { + X "$indent$(Esc-Xml $valStr)" } else { X "$indent$(Esc-Xml $valStr)" } @@ -1744,6 +1749,10 @@ function Parse-StructureShorthand { if ($seg -match '^(?i)(details|детали)$') { # Empty groupBy = detailed records $group | Add-Member -NotePropertyName "groupBy" -NotePropertyValue @() + } elseif ($seg -match '^(.+)\[(.+)\]$') { + # Named group: "ИмяГруппы[Поле]" + $group | Add-Member -NotePropertyName "name" -NotePropertyValue $Matches[1].Trim() + $group | Add-Member -NotePropertyName "groupBy" -NotePropertyValue @($Matches[2].Trim()) } else { $group | Add-Member -NotePropertyName "groupBy" -NotePropertyValue @($seg) } diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py index 35a86414..51ee0b27 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.py +++ b/.claude/skills/skd-compile/scripts/skd-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# skd-compile v1.4 — Compile 1C DCS from JSON +# skd-compile v1.5 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -702,6 +702,8 @@ def emit_param_value(lines, type_str, val, indent): lines.append(f'{indent}{esc_xml(val_str)}') elif val_str == 'true' or val_str == 'false': lines.append(f'{indent}{esc_xml(val_str)}') + elif re.match(r'^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена|ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.', val_str): + lines.append(f'{indent}{esc_xml(val_str)}') else: lines.append(f'{indent}{esc_xml(val_str)}') @@ -726,15 +728,18 @@ def emit_single_param(lines, p, parsed): # Value emit_param_value(lines, parsed.get('type', ''), parsed.get('value'), '\t\t') + # Hidden implies useRestriction=true + availableAsField=false + if parsed.get('hidden') is True: + parsed['availableAsField'] = False + parsed['useRestriction'] = True + # UseRestriction - if p is not None and not isinstance(p, str) and p.get('useRestriction') is True: + if parsed.get('useRestriction') is True or (p is not None and not isinstance(p, str) and p.get('useRestriction') is True): lines.append('\t\ttrue') # Expression if parsed.get('expression'): lines.append(f'\t\t{esc_xml(parsed["expression"])}') - - # Hidden implies availableAsField=false if parsed.get('hidden'): parsed['availableAsField'] = False @@ -1461,7 +1466,13 @@ def parse_structure_shorthand(s): if re.match(r'(?i)^(details|\u0434\u0435\u0442\u0430\u043b\u0438)$', seg): group['groupBy'] = [] else: - group['groupBy'] = [seg] + # Named group: "ИмяГруппы[Поле]" + m_named = re.match(r'^(.+)\[(.+)\]$', seg) + if m_named: + group['name'] = m_named.group(1).strip() + group['groupBy'] = [m_named.group(2).strip()] + else: + group['groupBy'] = [seg] if innermost is not None: group['children'] = [innermost] From 940eafb8e418cd4c1a2dc83cb864fa5da0e50b61 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 6 Apr 2026 15:02:22 +0300 Subject: [PATCH 11/94] fix(skd-edit): patch-query with empty replacement (delete substring) .strip()/.Trim() in batch-splitting was stripping the trailing space of the " => " separator, making " => " (delete) unrecognizable. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/skd-edit/scripts/skd-edit.ps1 | 4 +++- .claude/skills/skd-edit/scripts/skd-edit.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.claude/skills/skd-edit/scripts/skd-edit.ps1 b/.claude/skills/skd-edit/scripts/skd-edit.ps1 index 2f948692..cd281f75 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.ps1 +++ b/.claude/skills/skd-edit/scripts/skd-edit.ps1 @@ -1,4 +1,4 @@ -# skd-edit v1.3 — Atomic 1C DCS editor +# skd-edit v1.4 — Atomic 1C DCS editor # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -1445,6 +1445,8 @@ $corNs = "http://v8.1c.ru/8.1/data-composition-system/core" if ($Operation -eq "set-query" -or $Operation -eq "set-structure" -or $Operation -eq "add-dataSet") { $values = @($Value) +} elseif ($Operation -eq "patch-query") { + $values = @($Value -split ';;' | Where-Object { $_.Trim() }) } else { $values = @($Value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) } diff --git a/.claude/skills/skd-edit/scripts/skd-edit.py b/.claude/skills/skd-edit/scripts/skd-edit.py index ef06eccb..c271704c 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.py +++ b/.claude/skills/skd-edit/scripts/skd-edit.py @@ -1,4 +1,4 @@ -# skd-edit v1.3 — Atomic 1C DCS editor (Python port) +# skd-edit v1.4 — Atomic 1C DCS editor (Python port) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os @@ -1252,6 +1252,8 @@ xml_doc = tree.getroot() if operation in ("set-query", "set-structure", "add-dataSet"): values = [value_arg] +elif operation == "patch-query": + values = [v for v in value_arg.split(";;") if v.strip()] else: values = [v.strip() for v in value_arg.split(";;") if v.strip()] From d155086444a5164a2f0b34f4cb89588770a0ab83 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 6 Apr 2026 15:41:53 +0300 Subject: [PATCH 12/94] feat(skills): auto-detect XML format version from Configuration.xml When working with existing configs dumped from newer platforms (8.3.27+), XML files use version="2.20" instead of "2.17". Skills now detect the version from the nearest Configuration.xml walking up the directory tree, falling back to "2.17" if not found. This prevents format version mismatch errors during LoadConfigFromFiles. Updated skills (11): meta-compile, form-compile, form-add, template-add, cfe-borrow, epf-add-form, help-add, role-compile, subsystem-compile, interface-edit. Also fixed form-validate to accept version 2.20. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../skills/cfe-borrow/scripts/cfe-borrow.ps1 | 27 ++++++++++++++--- .../skills/cfe-borrow/scripts/cfe-borrow.py | 26 ++++++++++++++--- .../skills/epf-add-form/scripts/add-form.ps1 | 25 ++++++++++++++-- .../skills/epf-add-form/scripts/add-form.py | 25 ++++++++++++++-- .claude/skills/form-add/scripts/form-add.ps1 | 29 +++++++++++++++---- .claude/skills/form-add/scripts/form-add.py | 29 +++++++++++++++---- .../form-compile/scripts/form-compile.ps1 | 26 +++++++++++++++-- .../form-compile/scripts/form-compile.py | 24 +++++++++++++-- .../form-validate/scripts/form-validate.ps1 | 6 ++-- .../form-validate/scripts/form-validate.py | 6 ++-- .claude/skills/help-add/scripts/add-help.ps1 | 23 +++++++++++++-- .claude/skills/help-add/scripts/add-help.py | 23 +++++++++++++-- .../interface-edit/scripts/interface-edit.ps1 | 23 +++++++++++++-- .../interface-edit/scripts/interface-edit.py | 25 ++++++++++++++-- .../meta-compile/scripts/meta-compile.ps1 | 27 ++++++++++++++--- .../meta-compile/scripts/meta-compile.py | 29 ++++++++++++++++--- .../role-compile/scripts/role-compile.ps1 | 26 +++++++++++++++-- .../role-compile/scripts/role-compile.py | 25 ++++++++++++++-- .../scripts/subsystem-compile.ps1 | 23 +++++++++++++-- .../scripts/subsystem-compile.py | 22 ++++++++++++-- .../template-add/scripts/add-template.ps1 | 23 +++++++++++++-- .../template-add/scripts/add-template.py | 23 +++++++++++++-- 22 files changed, 450 insertions(+), 65 deletions(-) diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 index c186c3f9..099ddac2 100644 --- a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 +++ b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 @@ -1,4 +1,4 @@ -# cfe-borrow v1.2 — Borrow objects from configuration into extension (CFE) +# cfe-borrow v1.3 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][string]$ExtensionPath, @@ -316,6 +316,25 @@ function Expand-SelfClosingElement($container, $parentIndent) { } } +# --- 7b. Detect format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$script:formatVersion = Detect-FormatVersion $extDir + # --- 8. Namespaces declaration for object XML --- $script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' @@ -466,7 +485,7 @@ function Borrow-Form { $newFormUuid = [guid]::NewGuid().ToString() $formMetaSb = New-Object System.Text.StringBuilder $formMetaSb.AppendLine("") | Out-Null - $formMetaSb.AppendLine("") | Out-Null + $formMetaSb.AppendLine("") | Out-Null $formMetaSb.AppendLine("`t
") | Out-Null $formMetaSb.AppendLine("`t`t") | Out-Null $formMetaSb.AppendLine("`t`t") | Out-Null @@ -498,7 +517,7 @@ function Borrow-Form { $srcFormEl = $srcFormDoc.DocumentElement $formVersion = $srcFormEl.GetAttribute("version") - if (-not $formVersion) { $formVersion = "2.17" } + if (-not $formVersion) { $formVersion = $script:formatVersion } # Find direct children: form properties, AutoCommandBar, ChildItems $srcAutoCmd = $null @@ -1529,7 +1548,7 @@ function Build-BorrowedObjectXml { $sb = New-Object System.Text.StringBuilder $sb.AppendLine("") | Out-Null - $sb.AppendLine("") | Out-Null + $sb.AppendLine("") | Out-Null $sb.AppendLine("`t<${typeName} uuid=`"${newUuid}`">") | Out-Null # InternalInfo diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py index 0140da1c..7ba8ade9 100644 --- a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py +++ b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cfe-borrow v1.2 — Borrow objects from configuration into extension (CFE) +# cfe-borrow v1.3 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -254,6 +254,22 @@ XMLNS_DECL = ( ) +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + + def get_child_indent(container): if container.text and "\n" in container.text: after_nl = container.text.rsplit("\n", 1)[-1] @@ -365,6 +381,8 @@ def main(): cfg_resolved = os.path.abspath(cfg_path) cfg_dir = os.path.dirname(cfg_resolved) + format_version = detect_format_version(ext_dir) + # --- 2. Load extension Configuration.xml --- xml_parser = etree.XMLParser(remove_blank_text=False) tree = etree.parse(ext_resolved, xml_parser) @@ -501,7 +519,7 @@ def main(): lines = [] lines.append('') - lines.append(f'') + lines.append(f'') lines.append(f'\t<{type_name} uuid="{new_uuid_val}">') lines.append(internal_info_xml) lines.append("\t\t") @@ -1086,7 +1104,7 @@ def main(): new_form_uuid = new_guid() form_meta_lines = [ '', - f'', + f'', f'\t', '\t\t', '\t\t', @@ -1113,7 +1131,7 @@ def main(): src_form_tree = etree.parse(src_form_xml_path, src_form_parser) src_form_el = src_form_tree.getroot() - form_version = src_form_el.get("version", "2.17") + form_version = src_form_el.get("version", format_version) src_auto_cmd = None form_props = [] diff --git a/.claude/skills/epf-add-form/scripts/add-form.ps1 b/.claude/skills/epf-add-form/scripts/add-form.ps1 index 15266f7d..3befdccc 100644 --- a/.claude/skills/epf-add-form/scripts/add-form.ps1 +++ b/.claude/skills/epf-add-form/scripts/add-form.ps1 @@ -1,4 +1,4 @@ -# epf-add-form v1.0 — Add managed form to 1C processor +# epf-add-form v1.1 — Add managed form to 1C processor # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -16,6 +16,25 @@ param( $ErrorActionPreference = "Stop" +# --- Detect format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path + # --- Проверки --- $rootXmlPath = Join-Path $SrcDir "$ProcessorName.xml" @@ -52,7 +71,7 @@ $formUuid = [guid]::NewGuid().ToString() $formMetaXml = @" - + $FormName @@ -83,7 +102,7 @@ $formXmlPath = Join-Path $formExtDir "Form.xml" $formXml = @" - + true diff --git a/.claude/skills/epf-add-form/scripts/add-form.py b/.claude/skills/epf-add-form/scripts/add-form.py index 5ea16241..56926bf5 100644 --- a/.claude/skills/epf-add-form/scripts/add-form.py +++ b/.claude/skills/epf-add-form/scripts/add-form.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -# add-form v1.0 — Add managed form to 1C external data processor +# add-form v1.1 — Add managed form to 1C external data processor # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os +import re import sys import uuid @@ -12,6 +13,22 @@ from lxml import etree NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"} +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + + def save_xml_with_bom(tree, path): """Save XML tree to file with UTF-8 BOM.""" xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8") @@ -46,6 +63,8 @@ def main(): is_main = args.Main src_dir = args.SrcDir + format_version = detect_format_version(os.path.abspath(src_dir)) + # --- Checks --- root_xml_path = os.path.join(src_dir, f"{processor_name}.xml") @@ -92,7 +111,7 @@ def main(): ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"' ' xmlns:xs="http://www.w3.org/2001/XMLSchema"' ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' - ' version="2.17">\n' + f' version="{format_version}">\n' f'\t\n' '\t\t\n' f'\t\t\t{form_name}\n' @@ -139,7 +158,7 @@ def main(): ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"' ' xmlns:xs="http://www.w3.org/2001/XMLSchema"' ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' - ' version="2.17">\n' + f' version="{format_version}">\n' '\t\n' '\t\ttrue\n' '\t\n' diff --git a/.claude/skills/form-add/scripts/form-add.ps1 b/.claude/skills/form-add/scripts/form-add.ps1 index 0e0b6e82..2e57542f 100644 --- a/.claude/skills/form-add/scripts/form-add.ps1 +++ b/.claude/skills/form-add/scripts/form-add.ps1 @@ -1,4 +1,4 @@ -# form-add v1.2 — Add managed form to 1C config object +# form-add v1.3 — Add managed form to 1C config object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -16,6 +16,23 @@ param( $ErrorActionPreference = "Stop" +# --- Detect XML format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + # --- Фаза 1: Определение типа объекта --- # Resolve ObjectPath (directory → .xml) @@ -36,6 +53,8 @@ if (-not (Test-Path $ObjectPath)) { } $objectXmlFull = Resolve-Path $ObjectPath +$script:formatVersion = Detect-FormatVersion (Split-Path $objectXmlFull.Path -Parent) + $xmlDoc = New-Object System.Xml.XmlDocument $xmlDoc.PreserveWhitespace = $true $xmlDoc.Load($objectXmlFull.Path) @@ -159,7 +178,7 @@ if ($objectType -in $processorLikeTypes) { $formMetaXml = @" - + $FormName @@ -196,7 +215,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") { $formXml = @" - + true @@ -224,7 +243,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") { $formXml = @" - + true @@ -267,7 +286,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") { $formXml = @" - + true diff --git a/.claude/skills/form-add/scripts/form-add.py b/.claude/skills/form-add/scripts/form-add.py index e128de81..1b4d948c 100644 --- a/.claude/skills/form-add/scripts/form-add.py +++ b/.claude/skills/form-add/scripts/form-add.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -# form-add v1.2 — Add managed form to 1C config object +# form-add v1.3 — Add managed form to 1C config object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os +import re import sys import uuid @@ -15,6 +16,22 @@ NSMAP = { } +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + + def save_xml_with_bom(tree, path): """Save XML tree to file with UTF-8 BOM.""" xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8") @@ -67,6 +84,8 @@ def main(): sys.exit(1) object_xml_full = os.path.abspath(object_path) + format_version = detect_format_version(os.path.dirname(object_xml_full)) + parser_xml = etree.XMLParser(remove_blank_text=False) tree = etree.parse(object_xml_full, parser_xml) root = tree.getroot() @@ -171,7 +190,7 @@ def main(): ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"' ' xmlns:xs="http://www.w3.org/2001/XMLSchema"' ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' - ' version="2.17">\n' + f' version="{format_version}">\n' f'\t\n' '\t\t\n' f'\t\t\t{form_name}\n' @@ -225,7 +244,7 @@ def main(): form_xml = ( f'\n' - f'\n' + f'\n' '\t\n' '\t\ttrue\n' '\t\n' @@ -254,7 +273,7 @@ def main(): form_xml = ( f'\n' - f'\n' + f'\n' '\t\n' '\t\ttrue\n' '\t\n' @@ -297,7 +316,7 @@ def main(): form_xml = ( f'\n' - f'\n' + f'\n' '\t\n' '\t\ttrue\n' '\t\n' diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 5b6151f3..847530c8 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.1 — Compile 1C managed form from JSON +# form-compile v1.2 — Compile 1C managed form from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -11,6 +11,26 @@ param( $ErrorActionPreference = "Stop" [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +# --- Detect XML format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$script:outPathResolved = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location) $OutputPath } +$script:formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($script:outPathResolved)) + # --- 1. Load and validate JSON --- if (-not (Test-Path $JsonPath)) { @@ -1107,7 +1127,7 @@ if ($def.title) { # Header X '' -X '' +X '' # Oops — Title was emitted before header. Need to fix the order. # Actually, let me restructure: build the body into a separate buffer, then assemble @@ -1117,7 +1137,7 @@ $script:xml = New-Object System.Text.StringBuilder 8192 $script:nextId = 1 X '' -X '' +X '' # 12a. Title (from def.title or properties.title — must be multilingual XML) $formTitle = $def.title diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 37be10e6..d5050bc0 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.1 — Compile 1C managed form from JSON +# form-compile v1.2 — Compile 1C managed form from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -1002,6 +1002,22 @@ def emit_properties(lines, props, indent): lines.append(f'{indent}<{xml_name}>{val}') +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + + def main(): sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") @@ -1012,6 +1028,10 @@ def main(): parser.add_argument('-OutputPath', type=str, required=True) args = parser.parse_args() + # --- Detect XML format version --- + out_path_resolved = args.OutputPath if os.path.isabs(args.OutputPath) else os.path.join(os.getcwd(), args.OutputPath) + format_version = detect_format_version(os.path.dirname(out_path_resolved)) + # --- 1. Load and validate JSON --- json_path = args.JsonPath if not os.path.exists(json_path): @@ -1026,7 +1046,7 @@ def main(): lines = [] lines.append('') - lines.append('') + lines.append(f'') # Title form_title = defn.get('title') diff --git a/.claude/skills/form-validate/scripts/form-validate.ps1 b/.claude/skills/form-validate/scripts/form-validate.ps1 index c53a60ae..ad23601d 100644 --- a/.claude/skills/form-validate/scripts/form-validate.ps1 +++ b/.claude/skills/form-validate/scripts/form-validate.ps1 @@ -1,4 +1,4 @@ -# form-validate v1.3 — Validate 1C managed form +# form-validate v1.4 — Validate 1C managed form # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -126,10 +126,10 @@ if ($root.LocalName -ne "Form") { Report-Error "Root element is '$($root.LocalName)', expected 'Form'" } else { $version = $root.GetAttribute("version") - if ($version -eq "2.17") { + if ($version -eq "2.17" -or $version -eq "2.20") { Report-OK "Root element: Form version=$version" } elseif ($version) { - Report-Warn "Form version='$version' (expected 2.17)" + Report-Warn "Form version='$version' (expected 2.17 or 2.20)" } else { Report-Warn "Form version attribute missing" } diff --git a/.claude/skills/form-validate/scripts/form-validate.py b/.claude/skills/form-validate/scripts/form-validate.py index 91dac95b..e85eabb0 100644 --- a/.claude/skills/form-validate/scripts/form-validate.py +++ b/.claude/skills/form-validate/scripts/form-validate.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-validate v1.3 — Validate 1C managed form +# form-validate v1.4 — Validate 1C managed form # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -161,10 +161,10 @@ def main(): report_error(f"Root element is '{localname(root)}', expected 'Form'") else: version = root.get("version", "") - if version == "2.17": + if version in ("2.17", "2.20"): report_ok(f"Root element: Form version={version}") elif version: - report_warn(f"Form version='{version}' (expected 2.17)") + report_warn(f"Form version='{version}' (expected 2.17 or 2.20)") else: report_warn("Form version attribute missing") diff --git a/.claude/skills/help-add/scripts/add-help.ps1 b/.claude/skills/help-add/scripts/add-help.ps1 index 5211f700..ae067ec5 100644 --- a/.claude/skills/help-add/scripts/add-help.ps1 +++ b/.claude/skills/help-add/scripts/add-help.ps1 @@ -1,4 +1,4 @@ -# help-add v1.2 — Add built-in help to 1C object +# help-add v1.3 — Add built-in help to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -11,6 +11,25 @@ param( $ErrorActionPreference = "Stop" +# --- Detect format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path + # --- Проверки --- $objectDir = Join-Path $SrcDir $ObjectName @@ -35,7 +54,7 @@ $encBom = New-Object System.Text.UTF8Encoding($true) $helpXml = @" - + $Lang "@ diff --git a/.claude/skills/help-add/scripts/add-help.py b/.claude/skills/help-add/scripts/add-help.py index 03567724..ce6526d5 100644 --- a/.claude/skills/help-add/scripts/add-help.py +++ b/.claude/skills/help-add/scripts/add-help.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -# add-help v1.2 — Add built-in help to 1C object +# add-help v1.3 — Add built-in help to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os +import re import sys from lxml import etree @@ -11,6 +12,22 @@ from lxml import etree NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"} +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + + def save_xml_with_bom(tree, path): """Save XML tree to file with UTF-8 BOM.""" xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8") @@ -41,6 +58,8 @@ def main(): lang = args.Lang src_dir = args.SrcDir + format_version = detect_format_version(os.path.abspath(src_dir)) + # --- Checks --- object_dir = os.path.join(src_dir, object_name) @@ -62,7 +81,7 @@ def main(): '\n' + f' version="{format_version}">\n' f'\t{lang}\n' '' ) diff --git a/.claude/skills/interface-edit/scripts/interface-edit.ps1 b/.claude/skills/interface-edit/scripts/interface-edit.ps1 index adace28b..cefd9861 100644 --- a/.claude/skills/interface-edit/scripts/interface-edit.ps1 +++ b/.claude/skills/interface-edit/scripts/interface-edit.ps1 @@ -1,4 +1,4 @@ -# interface-edit v1.2 — Edit 1C CommandInterface.xml +# interface-edit v1.3 — Edit 1C CommandInterface.xml # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][string]$CIPath, @@ -23,6 +23,25 @@ if (-not [System.IO.Path]::IsPathRooted($CIPath)) { } $resolvedPath = $CIPath +# --- Detect format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($CIPath)) + # --- Namespaces --- $script:ciNs = "http://v8.1c.ru/8.3/xcf/extrnprops" $script:xrNs = "http://v8.1c.ru/8.3/xcf/readable" @@ -42,7 +61,7 @@ if (-not (Test-Path $CIPath)) { xmlns:xr="$($script:xrNs)" xmlns:xs="$($script:xsNs)" xmlns:xsi="$($script:xsiNs)" - version="2.17"> + version="$formatVersion"> "@ $utf8Bom = New-Object System.Text.UTF8Encoding($true) diff --git a/.claude/skills/interface-edit/scripts/interface-edit.py b/.claude/skills/interface-edit/scripts/interface-edit.py index 0401a78a..951b91a9 100644 --- a/.claude/skills/interface-edit/scripts/interface-edit.py +++ b/.claude/skills/interface-edit/scripts/interface-edit.py @@ -1,14 +1,31 @@ #!/usr/bin/env python3 -# interface-edit v1.2 — Edit 1C CommandInterface.xml +# interface-edit v1.3 — Edit 1C CommandInterface.xml # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json import os +import re import subprocess import sys from lxml import etree +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + + CI_NS = "http://v8.1c.ru/8.3/xcf/extrnprops" XR_NS = "http://v8.1c.ru/8.3/xcf/readable" XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" @@ -181,6 +198,10 @@ def main(): print("Either -DefinitionFile or -Operation is required", file=sys.stderr) sys.exit(1) + # --- Detect format version --- + ci_dir = os.path.dirname(os.path.abspath(args.CIPath)) + format_version = detect_format_version(ci_dir) + # --- Resolve path --- ci_path = args.CIPath if not os.path.isabs(ci_path): @@ -199,7 +220,7 @@ def main(): f'\txmlns:xr="{XR_NS}"\n' f'\txmlns:xs="{XS_NS}"\n' f'\txmlns:xsi="{XSI_NS}"\n' - f'\tversion="2.17">\n' + f'\tversion="{format_version}">\n' f'' ) with open(ci_path, "w", encoding="utf-8-sig") as fh: diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1 index 19615a16..02298d5a 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.ps1 +++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1 @@ -1,4 +1,4 @@ -# meta-compile v1.6 — Compile 1C metadata object from JSON +# meta-compile v1.7 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -2540,13 +2540,32 @@ function Emit-AddressingAttribute { $script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# --- 14a. Detect format version from existing Configuration.xml --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$script:formatVersion = Detect-FormatVersion $OutputDir + # --- 15. Main assembler --- $uuid = New-Guid-String # XML declaration X '' -X "" +X "" X "`t<$objType uuid=`"$uuid`">" # InternalInfo @@ -2908,7 +2927,7 @@ if ($objType -eq "ExchangePlan") { $contentPath = Join-Path $extDir "Content.xml" if (-not (Test-Path $contentPath)) { Ensure-ExtDir - $contentXml = "`r`n`r`n" + $contentXml = "`r`n`r`n" [System.IO.File]::WriteAllText($contentPath, $contentXml, $enc) $modulesCreated += $contentPath } @@ -2917,7 +2936,7 @@ if ($objType -eq "BusinessProcess") { $flowchartPath = Join-Path $extDir "Flowchart.xml" if (-not (Test-Path $flowchartPath)) { Ensure-ExtDir - $flowchartXml = "`r`n`r`n" + $flowchartXml = "`r`n`r`n" [System.IO.File]::WriteAllText($flowchartPath, $flowchartXml, $enc) $modulesCreated += $flowchartPath } diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py index 30068532..9589efc8 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.py +++ b/.claude/skills/meta-compile/scripts/meta-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-compile v1.6 — Compile 1C metadata object from JSON +# meta-compile v1.7 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -2202,6 +2202,27 @@ def emit_addressing_attribute(indent, addr_def): xmlns_decl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# --------------------------------------------------------------------------- +# 14a. Detect format version from existing Configuration.xml +# --------------------------------------------------------------------------- + +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + +format_version = detect_format_version(output_dir) + # --------------------------------------------------------------------------- # 15. Main assembler # --------------------------------------------------------------------------- @@ -2209,7 +2230,7 @@ xmlns_decl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8 obj_uuid = new_uuid() X('') -X(f'') +X(f'') X(f'\t<{obj_type} uuid="{obj_uuid}">') # InternalInfo @@ -2529,7 +2550,7 @@ if obj_type == 'ExchangePlan': content_path = os.path.join(ext_dir, 'Content.xml') if not os.path.isfile(content_path): ensure_ext_dir() - content_xml = '\r\n\r\n' + content_xml = f'\r\n\r\n' write_utf8_bom(content_path, content_xml) modules_created.append(content_path) @@ -2537,7 +2558,7 @@ if obj_type == 'BusinessProcess': flowchart_path = os.path.join(ext_dir, 'Flowchart.xml') if not os.path.isfile(flowchart_path): ensure_ext_dir() - flowchart_xml = '\r\n\r\n' + flowchart_xml = f'\r\n\r\n' write_utf8_bom(flowchart_path, flowchart_xml) modules_created.append(flowchart_path) diff --git a/.claude/skills/role-compile/scripts/role-compile.ps1 b/.claude/skills/role-compile/scripts/role-compile.ps1 index 508ba64c..01133076 100644 --- a/.claude/skills/role-compile/scripts/role-compile.ps1 +++ b/.claude/skills/role-compile/scripts/role-compile.ps1 @@ -1,4 +1,4 @@ -# role-compile v1.3 — Compile 1C role from JSON +# role-compile v1.4 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -505,6 +505,26 @@ if ($def.objects) { } } +# --- Detect format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir } +$formatVersion = Detect-FormatVersion $resolvedOutputDir + # --- 8. Generate UUID --- $uuid = [guid]::NewGuid().ToString() @@ -531,7 +551,7 @@ X ' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"' X ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"' X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"' X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' -X ' version="2.17">' +X ' version="$formatVersion">' X " " X ' ' X " $roleName" @@ -560,7 +580,7 @@ X '' X '' +X " xsi:type=`"Rights`" version=`"$formatVersion`">" # Global flags (defaults match typical 1C roles) $sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" } diff --git a/.claude/skills/role-compile/scripts/role-compile.py b/.claude/skills/role-compile/scripts/role-compile.py index 65dca59f..826fb2c9 100644 --- a/.claude/skills/role-compile/scripts/role-compile.py +++ b/.claude/skills/role-compile/scripts/role-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# role-compile v1.3 — Compile 1C role from JSON +# role-compile v1.4 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -9,6 +9,22 @@ import sys import uuid +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + + def esc_xml(s): return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') @@ -459,6 +475,9 @@ def main(): if not defn.get('objects') and defn.get('rights'): defn['objects'] = defn['rights'] + out_dir_resolved = args.OutputDir if os.path.isabs(args.OutputDir) else os.path.join(os.getcwd(), args.OutputDir) + format_version = detect_format_version(out_dir_resolved) + # --- 2. Parse all object entries --- parsed_objects = [] if defn.get('objects'): @@ -490,7 +509,7 @@ def main(): lines.append(' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"') lines.append(' xmlns:xs="http://www.w3.org/2001/XMLSchema"') lines.append(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') - lines.append(' version="2.17">') + lines.append(f' version="{format_version}">') lines.append(f' ') lines.append(' ') lines.append(f' {role_name}') @@ -516,7 +535,7 @@ def main(): lines.append('') + lines.append(f' xsi:type="Rights" version="{format_version}">') # Global flags sfno = str(defn['setForNewObjects']).lower() if defn.get('setForNewObjects') is not None else 'false' diff --git a/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 b/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 index 5580fa7b..ae4f1669 100644 --- a/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 +++ b/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 @@ -1,4 +1,4 @@ -# subsystem-compile v1.2 — Create 1C subsystem from JSON definition +# subsystem-compile v1.3 — Create 1C subsystem from JSON definition # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -267,12 +267,31 @@ if ($def.children) { foreach ($ch in $def.children) { $children += "$ch" } } +# --- Detect format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$formatVersion = Detect-FormatVersion $OutputDir + # --- 4. Build XML --- $uuid = New-Guid-String $indent = "`t`t`t" X '' -X '' +X '' X "`t" X "`t`t" diff --git a/.claude/skills/subsystem-compile/scripts/subsystem-compile.py b/.claude/skills/subsystem-compile/scripts/subsystem-compile.py index 031a1ad0..311be981 100644 --- a/.claude/skills/subsystem-compile/scripts/subsystem-compile.py +++ b/.claude/skills/subsystem-compile/scripts/subsystem-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# subsystem-compile v1.2 — Create 1C subsystem from JSON definition +# subsystem-compile v1.3 — Create 1C subsystem from JSON definition # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -10,6 +10,22 @@ import uuid import xml.etree.ElementTree as ET +def detect_format_version(d): + while d: + cfg_path = os.path.join(d, "Configuration.xml") + if os.path.isfile(cfg_path): + with open(cfg_path, "r", encoding="utf-8-sig") as f: + head = f.read(2000) + m = re.search(r']+version="(\d+\.\d+)"', head) + if m: + return m.group(1) + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return "2.17" + + def esc_xml(s): return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') @@ -169,6 +185,8 @@ def main(): type_part = CONTENT_TYPE_MAP[type_part] return f'{type_part}.{name_part}' + format_version = detect_format_version(output_dir) + # --- 3. Resolve defaults --- synonym = str(defn['synonym']) if defn.get('synonym') else split_camel_case(obj_name) comment = str(defn['comment']) if defn.get('comment') else '' @@ -205,7 +223,7 @@ def main(): lines = [] lines.append('') - lines.append('') + lines.append(f'') lines.append(f'\t') lines.append('\t\t') diff --git a/.claude/skills/template-add/scripts/add-template.ps1 b/.claude/skills/template-add/scripts/add-template.ps1 index c6253e5b..98ebd718 100644 --- a/.claude/skills/template-add/scripts/add-template.ps1 +++ b/.claude/skills/template-add/scripts/add-template.ps1 @@ -1,4 +1,4 @@ -# template-add v1.2 — Add template to 1C object +# template-add v1.3 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -59,13 +59,32 @@ New-Item -ItemType Directory -Path $templateExtDir -Force | Out-Null $encBom = New-Object System.Text.UTF8Encoding($true) +# --- Detect format version --- + +function Detect-FormatVersion([string]$dir) { + $d = $dir + while ($d) { + $cfgPath = Join-Path $d "Configuration.xml" + if (Test-Path $cfgPath) { + $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) + if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } + } + $parent = Split-Path $d -Parent + if ($parent -eq $d) { break } + $d = $parent + } + return "2.17" +} + +$formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path + # --- 1. Метаданные макета (Templates/.xml) --- $templateUuid = [guid]::NewGuid().ToString() $templateMetaXml = @" - +