# 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 (benchmark) 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: Bootstrap supply-chain regression shell: pwsh run: ./skills/scripts/test-bootstrap-supply-chain.ps1 - name: Bootstrap supply-chain regression (Windows PowerShell 5.1) if: runner.os == 'Windows' shell: powershell run: ./skills/scripts/test-bootstrap-supply-chain.ps1 - name: Parse contracts (route-scope + IDA lock) shell: pwsh run: ./skills/scripts/test-parse-contracts.ps1 - name: Journal PR title safety shell: pwsh run: ./skills/scripts/test-workflow-title-safety.ps1 - name: P0 friction regression (Windows PowerShell 5.1) if: runner.os == 'Windows' shell: powershell run: ./skills/scripts/test-p0-friction.ps1 - name: Client-neutral bootstrap/discovery (Windows PowerShell 5.1) if: runner.os == 'Windows' shell: powershell run: ./skills/scripts/test-client-neutral-bootstrap.ps1 - name: Offline sample case contract (Windows PowerShell 5.1) if: runner.os == 'Windows' shell: powershell run: | $scratch = Join-Path $env:RUNNER_TEMP ("reverse-skill-offline-" + [guid]::NewGuid().ToString('n')) New-Item -ItemType Directory -Force -Path $scratch | Out-Null $sample = Join-Path $scratch 'sample.apk' Set-Content -Path $sample -Value 'fixture' -Encoding ASCII ./skills/scripts/case-init.ps1 ` -Hint "offline apk" ` -CaseName "offline-sample" ` -ProjectRoot $scratch ` -Preset offline-sample ` -Sample $sample $scope = Join-Path $scratch 'work/offline-sample/scope.md' $raw = Get-Content $scope -Raw if ($raw -notmatch '(?m)^- mode: offline\r?$') { throw 'offline sample did not keep offline network mode' } if ($raw -notmatch '(?m)^- ready_for_act: true\r?$') { throw 'offline sample did not become ready_for_act' } ./skills/scripts/case-guard.ps1 -CaseRoot (Join-Path $scratch 'work/offline-sample') try { ./skills/scripts/case-init.ps1 ` -Hint "missing offline apk" ` -CaseName "missing-sample" ` -ProjectRoot $scratch ` -Preset offline-sample ` -Sample (Join-Path $scratch 'missing.apk') throw 'missing offline sample was accepted' } catch { if ($_.Exception.Message -eq 'missing offline sample was accepted') { throw } } ./skills/scripts/case-init.ps1 ` -Hint "pending offline apk" ` -CaseName "force-auth" ` -ProjectRoot $scratch ` -Sample $sample & powershell -NoProfile -ExecutionPolicy Bypass -File ./skills/scripts/case-guard.ps1 ` -CaseRoot (Join-Path $scratch 'work/force-auth') ` -Force $guardExit = $LASTEXITCODE if ($guardExit -eq 0) { throw '-Force bypassed auth.status hard gate' } $global:LASTEXITCODE = 0 - 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: Client-neutral bootstrap/discovery (Bash) shell: bash run: bash skills/scripts/test-client-neutral-bootstrap.sh - name: Structured routing parity (Bash) shell: bash run: | set -euo pipefail bash skills/scripts/test-routing.sh bash skills/scripts/test-bootstrap-manifest.sh bash skills/scripts/test-bash-workflow.sh scratch="$(mktemp -d)" trap 'rm -rf "$scratch"' EXIT # Fresh Linux journey: no pwsh required, artifacts stay in caller project. caller="$scratch/caller-project" mkdir -p "$caller" printf 'fixture' > "$scratch/sample.apk" ( cd "$caller" bash "$GITHUB_WORKSPACE/skills/scripts/master-route.sh" --hint "offline apk" bash "$GITHUB_WORKSPACE/skills/scripts/case-init.sh" \ --hint "offline apk" \ --case-name "caller-default" \ --preset offline-sample \ --sample "$scratch/sample.apk" ) test -f "$caller/work/caller-default/scope.md" grep -Eq '^- project_root: .*/caller-project$' "$caller/work/caller-default/scope.md" grep -Eq '^- mode: offline$' "$caller/work/caller-default/scope.md" grep -Eq '^- ready_for_act: true$' "$caller/work/caller-default/scope.md" bash skills/scripts/case-guard.sh --case-root "$caller/work/caller-default" test ! -e "$GITHUB_WORKSPACE/work/caller-default" if bash skills/scripts/case-init.sh \ --hint "missing offline apk" \ --case-name "missing-sample" \ --project-root "$caller" \ --preset offline-sample \ --sample "$scratch/missing.apk"; then echo "missing offline sample was accepted" >&2 exit 1 fi # Compatibility: legacy --package-root still pins the work root. 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" 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 "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 # --force is compatibility-only and must not bypass the hard auth gate. ( cd "$caller" bash "$GITHUB_WORKSPACE/skills/scripts/case-init.sh" \ --hint "pending offline apk" \ --case-name "force-auth" \ --sample "$scratch/sample.apk" ) if bash skills/scripts/case-guard.sh --case-root "$caller/work/force-auth" --force; then echo "case-guard --force bypassed auth.status hard gate" >&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 } - name: Non-ASCII .ps1 must carry a UTF-8 BOM (Windows PowerShell 5.1 safety) shell: pwsh run: | # PS 5.1 parses a BOM-less .ps1 as the system ANSI codepage, garbling # Chinese/em-dash string literals. Every non-ASCII script MUST have a BOM. $files = git ls-files '*.ps1' if (-not $files) { Write-Host 'No .ps1 files tracked'; exit 0 } $bad = 0 foreach ($f in $files) { $bytes = [System.IO.File]::ReadAllBytes((Resolve-Path $f)) $hasNonAscii = $false foreach ($b in $bytes) { if ($b -ge 0x80) { $hasNonAscii = $true; break } } if (-not $hasNonAscii) { continue } $hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF if ($hasBom) { Write-Host "BOM OK: $f" } else { $bad++ Write-Host "::error file=$f::non-ASCII .ps1 lacks a UTF-8 BOM; add one so literals survive Windows PowerShell 5.1" } } if ($bad -gt 0) { Write-Host "$bad file(s) need a UTF-8 BOM"; exit 1 } Write-Host 'All non-ASCII .ps1 carry a UTF-8 BOM' 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 - name: review_case.py unit tests shell: bash run: python3 skills/case-review/tests/test_review_case.py 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"