Skip to content

fix: enforce project policy reviews #29

fix: enforce project policy reviews

fix: enforce project policy reviews #29

Workflow file for this run

name: Validate Skills Marketplace
on:
push:
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: validate-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Install Claude Code validator
run: npm install --global @anthropic-ai/claude-code@2.1.207
- name: Validate Claude marketplace
run: claude plugin validate . --strict
- name: Validate shared plugin structure
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$claude = Get-Content .claude-plugin/marketplace.json -Raw | ConvertFrom-Json
$codex = Get-Content .agents/plugins/marketplace.json -Raw | ConvertFrom-Json
$claudeNames = @($claude.plugins.name)
$codexNames = @($codex.plugins.name)
if (($claudeNames -join "|") -ne ($codexNames -join "|")) {
throw "Claude and Codex plugin names or order differ."
}
$pluginDirectories = @(Get-ChildItem plugins -Directory)
if ($pluginDirectories.Count -ne $claudeNames.Count) {
throw "Catalog and plugin-directory counts differ."
}
$skillNames = [Collections.Generic.HashSet[string]]::new()
foreach ($entry in $claude.plugins) {
$pluginRoot = Join-Path $PWD ($entry.source -replace "^\./", "")
$manifestPath = Join-Path $pluginRoot ".codex-plugin/plugin.json"
if (-not (Test-Path -LiteralPath $manifestPath)) {
throw "Missing manifest for $($entry.name)."
}
$manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
if ($manifest.name -ne $entry.name) {
throw "Manifest name differs for $($entry.name)."
}
if ($manifest.description -ne $entry.description) {
throw "Manifest description differs for $($entry.name)."
}
if ($manifest.version -notmatch "^\d+\.\d+\.\d+$") {
throw "Manifest version is not SemVer for $($entry.name)."
}
foreach ($field in @("license", "homepage", "repository")) {
if ([string]::IsNullOrWhiteSpace($manifest.$field)) {
throw "Missing $field for $($entry.name)."
}
}
$skillsRoot = Join-Path $pluginRoot "skills"
foreach ($skillDirectory in Get-ChildItem $skillsRoot -Directory) {
$skillPath = Join-Path $skillDirectory.FullName "SKILL.md"
if (-not (Test-Path -LiteralPath $skillPath)) {
throw "Missing SKILL.md in $($skillDirectory.Name)."
}
$lines = [IO.File]::ReadAllLines($skillPath)
if ($lines.Count -lt 100 -or $lines.Count -gt 200) {
throw "$($skillDirectory.Name) has $($lines.Count) lines; expected 100-200."
}
if ($lines[0] -ne "---" -or $lines[3] -ne "---") {
throw "$($skillDirectory.Name) frontmatter must contain only name and description."
}
$name = ($lines[1] -replace "^name:\s*", "").Trim('"')
$description = ($lines[2] -replace "^description:\s*", "").Trim('"')
if ($name -ne $skillDirectory.Name) {
throw "Folder and frontmatter names differ for $($skillDirectory.Name)."
}
if ($description.Length -gt 200) {
throw "$name description exceeds 200 characters."
}
if (-not $skillNames.Add($name)) {
throw "Duplicate skill name: $name."
}
}
}
if (Test-Path -LiteralPath mcp) {
throw "The retired MCP source tree must not be restored."
}
Write-Host "Validated $($claudeNames.Count) plugins and $($skillNames.Count) standalone skills."
- name: Validate static site
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$html = Get-Content site/index.html -Raw
$ids = @([regex]::Matches($html, 'id="([^"]+)"') | ForEach-Object { $_.Groups[1].Value })
$hrefs = @([regex]::Matches($html, 'href="([^"]+)"') | ForEach-Object { $_.Groups[1].Value })
foreach ($href in $hrefs) {
if ($href.StartsWith("#") -and $ids -notcontains $href.Substring(1)) {
throw "Missing site anchor: $href."
}
if (-not $href.StartsWith("#") -and -not $href.StartsWith("http") -and -not $href.StartsWith("data:")) {
$path = ($href -split "#")[0]
if ($path -and -not (Test-Path (Join-Path site $path))) {
throw "Missing site asset: $href."
}
}
}
if (Test-Path -LiteralPath site/mcp) {
throw "Retired MCP compatibility pages must not be restored."
}
Write-Host "Validated static-site anchors and local assets."