# reverse-skill CI:路由回归 + 结构一致性 + 供应链 pin gate + 冒烟 # 矩阵:windows-latest(原生 powershell 5.1) + ubuntu-latest(pwsh + powershell shim) # 触发:所有分支(含 fork 的改进分支),PR 也触发 name: CI on: push: pull_request: jobs: routing-tests: name: routing tests (${{ matrix.os }}) strategy: fail-fast: false matrix: os: [windows-latest, ubuntu-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # 脚本内部以 `powershell` 调用子进程;Linux runner 只有 pwsh,做个 shim - name: powershell shim (linux) if: runner.os == 'Linux' shell: bash run: sudo ln -sf "$(command -v pwsh)" /usr/local/bin/powershell - name: Routing regression (163 cases) shell: pwsh run: ./skills/scripts/test-routing.ps1 - name: Routing coherence + supply-chain pin gate shell: pwsh run: ./skills/scripts/verify-routing-coherence.ps1 - name: Smoke (verify + parse + quick route) shell: pwsh run: ./skills/scripts/smoke.ps1 - name: INDEX.md up-to-date check shell: pwsh run: ./skills/scripts/extract-summaries.ps1 -Check - name: All JSON manifests valid shell: pwsh run: | Get-Content skills/scripts/bootstrap-manifest.json -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null Get-Content kali/scripts/bootstrap-manifest.json -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null Get-Content skills/config/routing.json -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null Get-Content skills/tests/routing-benchmark.json -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null Write-Host "All JSON valid" - name: Doc fact tables vs manifest (verify-doc-facts) shell: pwsh run: ./skills/scripts/verify-doc-facts.ps1 sh-syntax: name: shell script syntax check runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: bash -n all .sh shell: bash run: | set -e while IFS= read -r f; do bash -n "$f" echo "syntax OK: $f" done < <(git ls-files '*.sh') - name: Structured routing parity (Bash) shell: bash run: | set -euo pipefail bash skills/scripts/test-routing.sh scratch="$(mktemp -d)" trap 'rm -rf "$scratch"' EXIT if bash skills/scripts/case-init.sh \ --hint "offline apk" \ --case-name "../case-escape" \ --package-root "$scratch/project" \ --preset offline-sample \ --sample "$scratch/sample.apk"; then echo "case-init accepted an unsafe case name" >&2 exit 1 fi if bash skills/scripts/case-init.sh \ --hint "authorized web review" \ --case-name "invalid-network" \ --package-root "$scratch/project" \ --auth-granted \ --network-profile "internet" \ --target-url "https://example.test/"; then echo "case-init accepted an unsupported network profile" >&2 exit 1 fi bash skills/scripts/case-init.sh \ --hint "authorized web review" \ --case-name "network-default" \ --package-root "$scratch/project" \ --auth-granted \ --target-url "https://example.test/" grep -Eq '^- mode: authorized_target_only$' "$scratch/project/work/network-default/scope.md" grep -Eq '^- ready_for_act: true$' "$scratch/project/work/network-default/scope.md" bash skills/scripts/case-guard.sh --case-root "$scratch/project/work/network-default" bash skills/scripts/case-init.sh \ --hint "authorized web review" \ --case-name "uppercase-network" \ --package-root "$scratch/project" \ --auth-granted \ --network-profile "AUTHORIZED_TARGET_ONLY" \ --target-url "https://example.test/" grep -Eq '^- mode: authorized_target_only$' "$scratch/project/work/uppercase-network/scope.md" bash skills/scripts/case-init.sh \ --hint "pending review" \ --case-name "guard-section" \ --package-root "$scratch/project" \ --target-url "https://example.test/" cat >> "$scratch/project/work/guard-section/scope.md" <<'FAKE_FIELDS' ## notes - status: granted - mode: authorized_target_only - ready_for_act: true FAKE_FIELDS if bash skills/scripts/case-guard.sh --case-root "$scratch/project/work/guard-section"; then echo "case-guard accepted fields outside their contract sections" >&2 exit 1 fi - name: PowerShell syntax check (PSParser, all tracked .ps1) shell: pwsh run: | $files = git ls-files '*.ps1' if (-not $files) { Write-Host 'No .ps1 files tracked'; exit 0 } $failed = 0 foreach ($f in $files) { $tokens = $null $errors = $null [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $f), [ref]$tokens, [ref]$errors) | Out-Null if ($errors -and $errors.Count -gt 0) { $failed++ Write-Host "::error file=$f::$($errors.Count) parse error(s)" foreach ($e in $errors) { Write-Host (" L{0}:{1} {2}" -f $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Message) } } else { Write-Host "syntax OK: $f" } } if ($failed -gt 0) { exit 1 } leak-scan: name: field-journal leak scan runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Scan field-journal for un-anonymized secrets shell: pwsh run: ./skills/scripts/scan-leaks.ps1 -Path skills/field-journal case-contract: name: case contract test (ctf-demo) runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Review examples/ctf-demo under strict contract shell: bash run: python3 skills/case-review/scripts/review_case.py examples/ctf-demo --verify-hashes --strict version-check: name: version consistency runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: VERSION matches latest CHANGELOG release shell: pwsh run: | $v = (Get-Content VERSION -Raw).Trim() $cl = Get-Content CHANGELOG.md -Raw $m = [regex]::Match($cl, '(?m)^## \[(\d+\.\d+\.\d+)\]') if (-not $m.Success) { Write-Error 'No version header found in CHANGELOG'; exit 1 } $latest = $m.Groups[1].Value if ($v -ne $latest) { Write-Error "VERSION ($v) does not match latest CHANGELOG release ($latest)" exit 1 } Write-Host "Version OK: $v"