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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$ConfigPath, [Parameter(Mandatory=$true, Position=0)][Alias('Path')][string]$ConfigPath,
[ValidateSet("overview","brief","full")] [ValidateSet("overview","brief","full")]
[string]$Mode = "overview", [string]$Mode = "overview",
[Alias('Name')] [Alias('Name')]
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$ConfigPath, [string]$ConfigPath,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages.""" """Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages."""
import sys, os, argparse, re 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory, Position=0)]
[string]$ExtensionPath, [string]$ExtensionPath,
[Parameter(Mandatory)] [Parameter(Mandatory)]
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$ExtensionPath, [string]$ExtensionPath,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects.""" """Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
import sys, os, argparse, re 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects # Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$ObjectPath, [string]$ObjectPath,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects # 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$FormPath, [string]$FormPath,
[int]$Limit = 150, [int]$Limit = 150,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$FormPath, [string]$FormPath,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)][Alias('Path')][string]$CIPath, [Parameter(Mandatory, Position=0)][Alias('Path')][string]$CIPath,
[switch]$Detailed, [switch]$Detailed,
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates CommandInterface.xml sections, command references, order, duplicates.""" """Validates CommandInterface.xml sections, command references, order, duplicates."""
import sys, os, argparse, re 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath, [Parameter(Mandatory=$true, Position=0)][Alias('Path')][string]$ObjectPath,
[ValidateSet("overview","brief","full")] [ValidateSet("overview","brief","full")]
[string]$Mode = "overview", [string]$Mode = "overview",
[string]$Name, [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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$ObjectPath, [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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$TemplatePath, [string]$TemplatePath,
[string]$ProcessorName, [string]$ProcessorName,
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$TemplatePath, [string]$TemplatePath,
[string]$ProcessorName, [string]$ProcessorName,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges.""" """Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges."""
import sys, os, argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$RightsPath, [Parameter(Mandatory=$true, Position=0)][Alias('Path')][string]$RightsPath,
[switch]$ShowDenied, [switch]$ShowDenied,
[int]$Limit = 150, [int]$Limit = 150,
[int]$Offset = 0, [int]$Offset = 0,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$TemplatePath, [string]$TemplatePath,
[ValidateSet("overview", "query", "fields", "links", "calculated", "resources", "params", "variant", "trace", "templates", "full")] [ValidateSet("overview", "query", "fields", "links", "calculated", "resources", "params", "variant", "trace", "templates", "full")]
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$TemplatePath, [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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$SubsystemPath, [Parameter(Mandatory=$true, Position=0)][Alias('Path')][string]$SubsystemPath,
[ValidateSet("overview","content","ci","tree","full")] [ValidateSet("overview","content","ci","tree","full")]
[string]$Mode = "overview", [string]$Mode = "overview",
[string]$Name, [string]$Name,
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath, [Parameter(Mandatory, Position=0)][Alias('Path')][string]$SubsystemPath,
[switch]$Detailed, [switch]$Detailed,
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates subsystem XML file structure, properties, content items, child objects.""" """Validates subsystem XML file structure, properties, content items, child objects."""
import sys, os, argparse, re 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
@@ -18,9 +18,9 @@
.\web-info.ps1 -ApachePath "C:\tools\apache24" .\web-info.ps1 -ApachePath "C:\tools\apache24"
#> #>
[CmdletBinding()] [CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false, Position=0)]
[string]$ApachePath [string]$ApachePath
) )
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$PackagePath, [string]$PackagePath,
[string]$Package, [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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory, Position=0)]
[Alias('Path')] [Alias('Path')]
[string]$PackagePath, [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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os import os
+1
View File
@@ -14,6 +14,7 @@ const GUARDS = [
['check-inline-drift.mjs', 'общие inline-реализации: копии совпадают с эталонами'], ['check-inline-drift.mjs', 'общие inline-реализации: копии совпадают с эталонами'],
['check-type-maps.mjs', 'карты типов метаданных: согласованы со спецификацией'], ['check-type-maps.mjs', 'карты типов метаданных: согласованы со спецификацией'],
['check-format-versions.mjs', 'проверенный диапазон версий формата: согласован со спецификацией'], ['check-format-versions.mjs', 'проверенный диапазон версий формата: согласован со спецификацией'],
['check-positional-binding.mjs', 'read-only навыки: позиционным остаётся только путь ко входу'],
]; ];
let failed = 0; 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('Позиционным остаётся только путь ко входу; лишний позиционный аргумент падает и файл цел.');