fix(skills): в read-only навыках позиционным остаётся только путь ко входу

Тот же класс дефекта, что закрыт в role-validate, был возможен во всей семье
*-info / *-validate / cfe-diff: лишний позиционный аргумент связывается со
следующим параметром по порядку объявления. Остальные скрипты спасала
случайность типов — на втором слоте оказался [int]MaxErrors/Limit или строка
с [ValidateSet], которые падают на конвертации. Любая перестановка параметров
в param() открывала дыру заново, а снапшот-тесты сверяют вывод, а не связывание.

Во всех 21 скрипте объявлен [CmdletBinding(PositionalBinding=$false)], входной
путь помечен Position=0. Инвариант: анализирующий навык не пишет в файл, который
ему не назвали по имени. Документированные вызовы в репозитории все именованные,
поведение навыков не меняется — 835 кейсов зелёные на обоих рантаймах без дрейфа
снэпшотов.

Гард check-positional-binding.mjs держит инвариант статически (объявление и
единственный Position=0; в py-порте все add_argument именованные) и поведенчески
(лишний позиционный аргумент роняет вызов, канареечный файл остаётся цел).
Семья определяется по имени навыка, поэтому новый *-info/*-validate попадает под
гард сам. На состоянии до правки гард падает на role-validate обеими проверками.

Версии обоих портов подняты синхронно; из шапок убраны чужие хвосты.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-18 14:59:52 +03:00
co-authored by Claude Opus 5
parent 83d63fac47
commit 9e883d7f4f
42 changed files with 227 additions and 59 deletions
+3 -2
View File
@@ -1,7 +1,8 @@
# cf-info v1.5 — Compact summary of 1C configuration root
# cf-info v1.6 — Compact summary of 1C configuration root
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$ConfigPath,
[Parameter(Mandatory=$true, Position=0)][Alias('Path')][string]$ConfigPath,
[ValidateSet("overview","brief","full")]
[string]$Mode = "overview",
[Alias('Name')]
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cf-info v1.5 — Compact summary of 1C configuration root
# cf-info v1.6 — Compact summary of 1C configuration root
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,7 +1,8 @@
# cf-validate v1.7 — Validate 1C configuration root structure
# cf-validate v1.8 — Validate 1C configuration root structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position=0)]
[Alias('Path')]
[string]$ConfigPath,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cf-validate v1.7 — Validate 1C configuration XML structure
# cf-validate v1.8 — Validate 1C configuration XML structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages."""
import sys, os, argparse, re
+3 -2
View File
@@ -1,7 +1,8 @@
# cfe-diff v1.2 — Analyze and compare 1C configuration extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
# cfe-diff v1.3 — Analyze and compare 1C configuration extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position=0)]
[string]$ExtensionPath,
[Parameter(Mandatory)]
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cfe-diff v1.2 — Analyze and compare 1C configuration extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
# cfe-diff v1.3 — Analyze and compare 1C configuration extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,7 +1,8 @@
# cfe-validate v1.13 — Validate 1C configuration extension structure (CFE)
# cfe-validate v1.14 — Validate 1C configuration extension structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position=0)]
[Alias('Path')]
[string]$ExtensionPath,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cfe-validate v1.13 — Validate 1C configuration extension XML structure (CFE)
# cfe-validate v1.14 — Validate 1C configuration extension XML structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
import sys, os, argparse, re
@@ -1,8 +1,9 @@
# epf-validate v1.5 — Validate 1C external data processor / report structure
# epf-validate v1.6 — Validate 1C external data processor / report structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position=0)]
[Alias('Path')]
[string]$ObjectPath,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# epf-validate v1.5 — Validate 1C external data processor / report structure
# epf-validate v1.6 — Validate 1C external data processor / report structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
@@ -1,7 +1,8 @@
# form-info v1.7 — Analyze 1C managed form structure (+единое имя хелпера состояния поддержки)
# form-info v1.8 — Analyze 1C managed form structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$true)]
[Parameter(Mandatory=$true, Position=0)]
[Alias('Path')]
[string]$FormPath,
[int]$Limit = 150,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-info v1.7 — Analyze 1C managed form structure (+единое имя хелпера состояния поддержки)
# form-info v1.8 — Analyze 1C managed form structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,7 +1,8 @@
# form-validate v1.17 — Validate 1C managed form
# form-validate v1.18 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position=0)]
[Alias('Path')]
[string]$FormPath,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-validate v1.17 — Validate 1C managed form
# form-validate v1.18 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,7 +1,8 @@
# interface-validate v1.3 — Validate 1C CommandInterface.xml structure (+Report-*: общий эталон вывода валидаторов)
# interface-validate v1.4 — Validate 1C CommandInterface.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
[Parameter(Mandatory, Position=0)][Alias('Path')][string]$CIPath,
[switch]$Detailed,
[int]$MaxErrors = 30,
[string]$OutFile
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# interface-validate v1.3 — Validate 1C CommandInterface.xml structure (+Report-*: общий эталон вывода валидаторов)
# interface-validate v1.4 — Validate 1C CommandInterface.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates CommandInterface.xml sections, command references, order, duplicates."""
import sys, os, argparse, re
@@ -1,7 +1,8 @@
# meta-info v1.10 — Compact summary of 1C metadata object (+единое имя хелпера состояния поддержки)
# meta-info v1.11 — Compact summary of 1C metadata object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath,
[Parameter(Mandatory=$true, Position=0)][Alias('Path')][string]$ObjectPath,
[ValidateSet("overview","brief","full")]
[string]$Mode = "overview",
[string]$Name,
@@ -1,4 +1,4 @@
# meta-info v1.10 — Compact summary of 1C metadata object (Python port) (+единое имя хелпера состояния поддержки)
# meta-info v1.11 — Compact summary of 1C metadata object (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -1,7 +1,8 @@
# meta-validate v1.19 — Validate 1C metadata object structure
# meta-validate v1.20 — Validate 1C metadata object structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position=0)]
[Alias('Path')]
[string]$ObjectPath,
@@ -1,4 +1,4 @@
# meta-validate v1.19 — Validate 1C metadata object structure (Python port)
# meta-validate v1.20 — Validate 1C metadata object structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
+3 -1
View File
@@ -1,6 +1,8 @@
# mxl-info v1.4 — Analyze 1C spreadsheet structure (+единое имя хелпера состояния поддержки)
# mxl-info v1.5 — Analyze 1C spreadsheet structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Position=0)]
[Alias('Path')]
[string]$TemplatePath,
[string]$ProcessorName,
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# mxl-info v1.4 — Analyze 1C spreadsheet structure (+единое имя хелпера состояния поддержки)
# mxl-info v1.5 — Analyze 1C spreadsheet structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,6 +1,8 @@
# mxl-validate v1.7 — Validate 1C spreadsheet
# mxl-validate v1.8 — Validate 1C spreadsheet
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Position=0)]
[Alias('Path')]
[string]$TemplatePath,
[string]$ProcessorName,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# mxl-validate v1.7 — Validate 1C spreadsheet document Template.xml
# mxl-validate v1.8 — Validate 1C spreadsheet document Template.xml
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges."""
import sys, os, argparse
@@ -1,7 +1,8 @@
# role-info v1.4 — Analyze 1C role rights (+единое имя хелпера состояния поддержки)
# role-info v1.5 — Analyze 1C role rights
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$RightsPath,
[Parameter(Mandatory=$true, Position=0)][Alias('Path')][string]$RightsPath,
[switch]$ShowDenied,
[int]$Limit = 150,
[int]$Offset = 0,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# role-info v1.4 — Analyze 1C role rights (+единое имя хелпера состояния поддержки)
# role-info v1.5 — Analyze 1C role rights
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
+3 -2
View File
@@ -1,7 +1,8 @@
# skd-info v1.11 — Analyze 1C DCS structure (+единое имя хелпера состояния поддержки)
# skd-info v1.12 — Analyze 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$true)]
[Parameter(Mandatory=$true, Position=0)]
[Alias('Path')]
[string]$TemplatePath,
[ValidateSet("overview", "query", "fields", "links", "calculated", "resources", "params", "variant", "trace", "templates", "full")]
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# skd-info v1.11 — Analyze 1C DCS structure (+единое имя хелпера состояния поддержки)
# skd-info v1.12 — Analyze 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,7 +1,8 @@
# skd-validate v1.3 — Validate 1C DCS structure
# skd-validate v1.4 — Validate 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position=0)]
[Alias('Path')]
[string]$TemplatePath,
@@ -1,4 +1,4 @@
# skd-validate v1.3 — Validate 1C DCS structure (Python port)
# skd-validate v1.4 — Validate 1C DCS structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -1,7 +1,8 @@
# subsystem-info v1.4 — Compact summary of 1C subsystem structure (+единое имя хелпера состояния поддержки)
# subsystem-info v1.5 — Compact summary of 1C subsystem structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$SubsystemPath,
[Parameter(Mandatory=$true, Position=0)][Alias('Path')][string]$SubsystemPath,
[ValidateSet("overview","content","ci","tree","full")]
[string]$Mode = "overview",
[string]$Name,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# subsystem-info v1.4 — Compact summary of 1C subsystem structure (+единое имя хелпера состояния поддержки)
# subsystem-info v1.5 — Compact summary of 1C subsystem structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,7 +1,8 @@
# subsystem-validate v1.4 — Validate 1C subsystem XML structure (+Report-*: общий эталон вывода валидаторов)
# subsystem-validate v1.5 — Validate 1C subsystem XML structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath,
[Parameter(Mandatory, Position=0)][Alias('Path')][string]$SubsystemPath,
[switch]$Detailed,
[int]$MaxErrors = 30,
[string]$OutFile
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# subsystem-validate v1.4 — Validate 1C subsystem XML structure (+Report-*: общий эталон вывода валидаторов)
# subsystem-validate v1.5 — Validate 1C subsystem XML structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates subsystem XML file structure, properties, content items, child objects."""
import sys, os, argparse, re
+3 -3
View File
@@ -1,4 +1,4 @@
# web-info v1.4 — Apache & 1C publication status
# web-info v1.5 — Apache & 1C publication status
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<#
.SYNOPSIS
@@ -18,9 +18,9 @@
.\web-info.ps1 -ApachePath "C:\tools\apache24"
#>
[CmdletBinding()]
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$false)]
[Parameter(Mandatory=$false, Position=0)]
[string]$ApachePath
)
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# web-info v1.4 — Apache & 1C publication status
# web-info v1.5 — Apache & 1C publication status
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""
@@ -1,7 +1,8 @@
# xdto-info v1.1 — Analyze 1C XDTO package structure
# xdto-info v1.2 — Analyze 1C XDTO package structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$true)]
[Parameter(Mandatory=$true, Position=0)]
[Alias('Path')]
[string]$PackagePath,
[string]$Package,
@@ -1,4 +1,4 @@
# xdto-info v1.1 — Analyze 1C XDTO package structure (Python port)
# xdto-info v1.2 — Analyze 1C XDTO package structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -1,7 +1,8 @@
# xdto-validate v1.3 — Validate a 1C XDTO package (+Report-*: общий эталон вывода валидаторов)
# xdto-validate v1.4 — Validate a 1C XDTO package
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position=0)]
[Alias('Path')]
[string]$PackagePath,
@@ -1,4 +1,4 @@
# xdto-validate v1.3 — Validate a 1C XDTO package (Python port) (+Report-*: общий эталон вывода валидаторов)
# xdto-validate v1.4 — Validate a 1C XDTO package (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
+1
View File
@@ -14,6 +14,7 @@ const GUARDS = [
['check-inline-drift.mjs', 'общие inline-реализации: копии совпадают с эталонами'],
['check-type-maps.mjs', 'карты типов метаданных: согласованы со спецификацией'],
['check-format-versions.mjs', 'проверенный диапазон версий формата: согласован со спецификацией'],
['check-positional-binding.mjs', 'read-only навыки: позиционным остаётся только путь ко входу'],
];
let failed = 0;
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env node
// Инвариант: анализирующий навык (*-info, *-validate, cfe-diff) не пишет в файл, который ему
// не назвали по имени. Позиционным остаётся только путь ко входу.
//
// Без этого лишний позиционный аргумент связывается со следующим параметром по порядку
// объявления. У role-validate им был -OutFile, и вызов вида
// role-validate.ps1 "Roles/Роль" "Roles/Роль.xml"
// перезаписывал XML роли текстом валидационного отчёта. Остальных скриптов семьи спасала
// случайность типов ([int]MaxErrors/Limit, строка с [ValidateSet] — падают на конвертации),
// то есть любая перестановка параметров в param() открывала дыру заново.
// Снапшот-тесты этого не видят: они сверяют вывод, а не связывание аргументов.
//
// Две проверки. Статическая: [CmdletBinding(PositionalBinding=$false)] объявлен, позиционный
// параметр не более одного и это Position=0; в py-порте все add_argument именованные.
// Поведенческая: лишний позиционный аргумент, указывающий на канареечный файл, роняет вызов
// и файл остаётся байт-в-байт. Валидные фикстуры не нужны — связывание параметров происходит
// до тела скрипта. Выход 1 при нарушении. Запуск: node tests/skills/check-positional-binding.mjs [--runtime python]
import { spawnSync } from 'node:child_process';
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdtempSync, rmSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { tmpdir } from 'node:os';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const SKILLS_DIR = join(ROOT, '.claude', 'skills');
const IS_WIN = process.platform === 'win32';
// PowerShell вне Windows не исполняется — это природа платформы, а не пробел в покрытии
// (см. debug/macmini-testing.md).
const requested = process.argv.includes('--runtime')
? [process.argv[process.argv.indexOf('--runtime') + 1] === 'python' ? 'python' : 'powershell']
: ['powershell', 'python'];
const runtimes = requested.filter(rt => rt !== 'powershell' || IS_WIN);
if (requested.includes('powershell') && !IS_WIN) {
console.log(`[powershell] пропущен: PowerShell не исполняется на ${process.platform}`);
}
if (runtimes.length === 0) {
console.log('Нечего проверять: запрошен только powershell, а он на этой ОС не исполняется.');
process.exit(1);
}
const PY = process.env.PYTHON || (IS_WIN ? 'python' : 'python3');
// Семья read-only навыков определяется по имени, а не списком: новый *-info/*-validate
// попадает под гард сам, без правки этого файла.
const isReadOnly = name => /-(info|validate)$/.test(name) || name === 'cfe-diff';
const skills = readdirSync(SKILLS_DIR)
.filter(isReadOnly)
.filter(name => existsSync(join(SKILLS_DIR, name, 'scripts', `${name}.ps1`)))
.sort();
if (skills.length === 0) {
console.error('Не найдено ни одного read-only навыка со скриптом — гард потерял цель.');
process.exit(1);
}
const violations = [];
const fail = (skill, msg) => violations.push(`${skill}: ${msg}`);
// --- 1. Статическая проверка объявлений ---
function paramBlock(text) {
const start = text.indexOf('\nparam(\n');
if (start < 0) return null;
const end = text.indexOf('\n)\n', start);
return end < 0 ? null : text.slice(start, end);
}
for (const skill of skills) {
const ps1Path = join(SKILLS_DIR, skill, 'scripts', `${skill}.ps1`);
const ps1 = readFileSync(ps1Path, 'utf8');
if (!/\[CmdletBinding\(\s*PositionalBinding\s*=\s*\$false\s*\)\]\s*\nparam\(/.test(ps1)) {
fail(skill, '.ps1: нет [CmdletBinding(PositionalBinding=$false)] перед param()');
}
const block = paramBlock(ps1);
if (block === null) {
fail(skill, '.ps1: не разобран блок param()');
} else {
const positions = [...block.matchAll(/Position\s*=\s*(\d+)/g)].map(m => m[1]);
if (positions.length > 1) {
fail(skill, `.ps1: позиционных параметров больше одного (Position=${positions.join(', ')})`);
} else if (positions.length === 1 && positions[0] !== '0') {
fail(skill, `.ps1: единственный позиционный параметр должен быть Position=0, а не Position=${positions[0]}`);
}
}
const pyPath = join(SKILLS_DIR, skill, 'scripts', `${skill}.py`);
if (existsSync(pyPath)) {
const py = readFileSync(pyPath, 'utf8');
for (const m of py.matchAll(/add_argument\(\s*(['"])([^'"]+)\1/g)) {
if (!m[2].startsWith('-')) {
fail(skill, `.py: позиционный аргумент argparse '${m[2]}' — все параметры должны быть именованными`);
}
}
}
}
// --- 2. Поведенческая проверка: лишний позиционный аргумент не трогает файл ---
const CANARY = 'канарейка: этот файл не должен быть перезаписан отчётом навыка\n';
const work = mkdtempSync(join(tmpdir(), 'posbind-'));
try {
for (const runtime of runtimes) {
for (const skill of skills) {
const ext = runtime === 'python' ? '.py' : '.ps1';
const script = join(SKILLS_DIR, skill, 'scripts', `${skill}${ext}`);
if (!existsSync(script)) continue;
const canary = join(work, `${skill}-${runtime}.canary`);
writeFileSync(canary, CANARY, 'utf8');
// Первый аргумент — заведомо несуществующий вход, второй — лишний: связывание
// параметров происходит до тела скрипта, поэтому до чтения входа дело не доходит.
const bogus = join(work, 'нет-такого-пути');
const [cmd, argv] = runtime === 'python'
? [PY, [script, bogus, canary]]
: ['powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', script, bogus, canary]];
const r = spawnSync(cmd, argv, { cwd: work, encoding: 'utf8' });
if (r.status === 0) {
fail(skill, `[${runtime}] лишний позиционный аргумент принят без ошибки`);
}
if (readFileSync(canary, 'utf8') !== CANARY) {
fail(skill, `[${runtime}] лишний позиционный аргумент ПЕРЕЗАПИСАЛ указанный файл`);
}
}
}
} finally {
rmSync(work, { recursive: true, force: true });
}
// --- Итог ---
if (violations.length) {
console.error(`Нарушений: ${violations.length}\n`);
for (const v of violations) console.error(` ${v}`);
process.exit(1);
}
console.log(`OK — ${skills.length} read-only навыков, рантаймы: ${runtimes.join(', ')}.`);
console.log('Позиционным остаётся только путь ко входу; лишний позиционный аргумент падает и файл цел.');