Skip to content

Split MCP Gateway into download and start steps with gh CLI authentic… #27889

Split MCP Gateway into download and start steps with gh CLI authentic…

Split MCP Gateway into download and start steps with gh CLI authentic… #27889

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
paths:
- '**.go'
- 'pkg/workflow/**'
- 'actions/**'
- '.github/workflows/ci.yml'
- '.github/workflows/**/*.md'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-test
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Verify dependencies
run: go mod verify
- name: Run unit tests with coverage
run: |
go test -v -parallel=8 -timeout=3m -tags '!integration' -run='^Test' -coverprofile=coverage.out -json ./... | tee test-result-unit.json
go tool cover -html=coverage.out -o coverage.html
# Coverage reports for recent builds only - 7 days is sufficient for debugging recent changes
- name: Upload coverage report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-report
path: coverage.html
retention-days: 7
- name: Upload unit test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-result-unit
path: test-result-unit.json
retention-days: 14
integration:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
test-group:
- name: "CLI Compile & Poutine"
packages: "./pkg/cli"
pattern: "^TestCompile[^W]|TestPoutine" # Exclude TestCompileWorkflows to avoid duplicates
- name: "CLI MCP Playwright"
packages: "./pkg/cli"
pattern: "TestMCPInspectPlaywright"
- name: "CLI MCP Gateway"
packages: "./pkg/cli"
pattern: "TestMCPGateway"
- name: "CLI MCP Other"
packages: "./pkg/cli"
pattern: "TestMCPAdd|TestMCPInspectGitHub|TestMCPServer|TestMCPConfig"
- name: "CLI Logs & Firewall"
packages: "./pkg/cli"
pattern: "TestLogs|TestFirewall|TestNoStopTime|TestLocalWorkflow"
- name: "CLI Progress Flag" # Isolate slow test (~65s for TestProgressFlagSignature)
packages: "./pkg/cli"
pattern: "TestProgressFlagSignature"
- name: "CLI HTTP MCP Connect" # Isolate slow HTTP MCP connection tests (~43s)
packages: "./pkg/cli"
pattern: "TestConnectHTTPMCPServer"
- name: "CLI Compile Workflows" # Isolate slow workflow compilation test
packages: "./pkg/cli"
pattern: "TestCompileWorkflows_EmptyMarkdown"
- name: "CLI Security Tools" # Group security tool compilation tests
packages: "./pkg/cli"
pattern: "TestCompileWithZizmor|TestCompileWithPoutine|TestCompileWithPoutineAndZizmor"
- name: "CLI Add & List Commands"
packages: "./pkg/cli"
pattern: "^TestAdd|^TestList"
- name: "CLI Update Command"
packages: "./pkg/cli"
pattern: "^TestUpdate"
- name: "CLI Audit & Inspect"
packages: "./pkg/cli"
pattern: "^TestAudit|^TestInspect"
- name: "CLI Completion & Other" # Remaining catch-all (reduced from original)
packages: "./pkg/cli"
pattern: "" # Catch-all for tests not matched by other CLI patterns
skip_pattern: "^TestCompile[^W]|TestPoutine|TestMCPInspectPlaywright|TestMCPGateway|TestMCPAdd|TestMCPInspectGitHub|TestMCPServer|TestMCPConfig|TestLogs|TestFirewall|TestNoStopTime|TestLocalWorkflow|TestProgressFlagSignature|TestConnectHTTPMCPServer|TestCompileWorkflows_EmptyMarkdown|TestCompileWithZizmor|TestCompileWithPoutine|TestCompileWithPoutineAndZizmor|^TestAdd|^TestList|^TestUpdate|^TestAudit|^TestInspect"
- name: "Workflow Compiler"
packages: "./pkg/workflow"
pattern: "TestCompile|TestWorkflow|TestGenerate|TestParse"
- name: "Workflow Tools & MCP"
packages: "./pkg/workflow"
pattern: "TestMCP|TestTool|TestSkill|TestPlaywright|TestFirewall"
- name: "Workflow Validation"
packages: "./pkg/workflow"
pattern: "TestValidat|TestLock|TestError|TestWarning"
- name: "Workflow Safe Outputs"
packages: "./pkg/workflow"
pattern: "SafeOutputs|CreatePullRequest|OutputLabel|HasSafeOutputs"
- name: "Workflow GitHub & Git"
packages: "./pkg/workflow"
pattern: "GitHub|Git|PushToPullRequest|BuildFromAllowed"
- name: "Workflow Rendering & Bundling"
packages: "./pkg/workflow"
pattern: "Render|Bundle|Script|WritePromptText"
- name: "Workflow Cache"
packages: "./pkg/workflow"
pattern: "^TestCache|TestCacheDependencies|TestCacheKey|TestValidateCache"
- name: "Workflow Actions Pin Validation"
packages: "./pkg/workflow"
pattern: "^TestActionPinSHAsMatchVersionTags"
- name: "Workflow Actions & Containers"
packages: "./pkg/workflow"
pattern: "^TestAction[^P]|Container"
- name: "Workflow Dependabot & Security"
packages: "./pkg/workflow"
pattern: "Dependabot|Security|PII"
- name: "CMD Tests" # All cmd/gh-aw integration tests
packages: "./cmd/gh-aw"
pattern: ""
skip_pattern: "" # No other groups cover cmd tests
- name: "Parser Remote Fetch & Cache"
packages: "./pkg/parser"
pattern: "TestDownloadFileFromGitHub|TestResolveIncludePath|TestDownloadIncludeFromWorkflowSpec|TestImportCache"
- name: "Parser Location & Validation"
packages: "./pkg/parser"
pattern: "" # Catch-all for tests not matched by other Parser patterns
skip_pattern: "TestDownloadFileFromGitHub|TestResolveIncludePath|TestDownloadIncludeFromWorkflowSpec|TestImportCache"
- name: "Workflow Permissions"
packages: "./pkg/workflow"
pattern: "TestPermissions|TestPackageExtractor|TestCollectPackagesFromWorkflow"
- name: "Workflow Misc Part 1" # Split large catch-all into two balanced groups
packages: "./pkg/workflow"
pattern: "TestAgent|TestCopilot|TestCustom|TestEngine|TestModel|TestNetwork|TestOpenAI|TestProvider"
- name: "Workflow String & Sanitization"
packages: "./pkg/workflow"
pattern: "String|Sanitize|Normalize|Trim|Clean|Format"
- name: "Workflow Runtime & Setup"
packages: "./pkg/workflow"
pattern: "Runtime|Setup|Install|Download|Version|Binary"
- name: "Workflow Misc Part 2" # Remaining workflow tests
packages: "./pkg/workflow"
pattern: ""
skip_pattern: "TestCompile|TestWorkflow|TestGenerate|TestParse|TestMCP|TestTool|TestSkill|TestPlaywright|TestFirewall|TestValidat|TestLock|TestError|TestWarning|SafeOutputs|CreatePullRequest|OutputLabel|HasSafeOutputs|GitHub|Git|PushToPullRequest|BuildFromAllowed|Render|Bundle|Script|WritePromptText|^TestCache|TestCacheDependencies|TestCacheKey|TestValidateCache|^TestActionPinSHAsMatchVersionTags|^TestAction[^P]|Container|Dependabot|Security|PII|TestPermissions|TestPackageExtractor|TestCollectPackagesFromWorkflow|TestAgent|TestCopilot|TestCustom|TestEngine|TestModel|TestNetwork|TestOpenAI|TestProvider|String|Sanitize|Normalize|Trim|Clean|Format|Runtime|Setup|Install|Download|Version|Binary"
- name: "AWMG Gateway Tests" # MCP gateway integration tests
packages: "./pkg/awmg"
pattern: ""
concurrency:
group: ci-${{ github.ref }}-integration-${{ matrix.test-group.name }}
cancel-in-progress: true
name: "Integration: ${{ matrix.test-group.name }}"
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Verify dependencies
run: go mod verify
- name: Run integration tests - ${{ matrix.test-group.name }}
run: |
# Sanitize the test group name for use in filename
SAFE_NAME=$(echo "${{ matrix.test-group.name }}" | sed 's/[^a-zA-Z0-9]/-/g' | sed 's/--*/-/g')
if [ -z "${{ matrix.test-group.pattern }}" ]; then
# Catch-all group: run with -skip to exclude tests matched by other groups
if [ -n "${{ matrix.test-group.skip_pattern || '' }}" ]; then
go test -v -parallel=8 -timeout=5m -tags 'integration' -skip '${{ matrix.test-group.skip_pattern }}' -json ${{ matrix.test-group.packages }} | tee "test-result-integration-${SAFE_NAME}.json"
else
go test -v -parallel=8 -timeout=5m -tags 'integration' -json ${{ matrix.test-group.packages }} | tee "test-result-integration-${SAFE_NAME}.json"
fi
else
go test -v -parallel=8 -timeout=5m -tags 'integration' -run '${{ matrix.test-group.pattern }}' -json ${{ matrix.test-group.packages }} | tee "test-result-integration-${SAFE_NAME}.json"
fi
- name: Upload integration test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-result-integration-${{ matrix.test-group.name }}
path: test-result-integration-*.json
retention-days: 14
update:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-update
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Verify dependencies
run: go mod verify
- name: Build gh-aw binary
run: make build
- name: Test update command (dry-run)
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "Testing update command to ensure it runs without issues..."
# Run update with verbose flag to check for updates without making changes
# The command checks for gh-aw updates, action updates, and workflow updates
./gh-aw update --verbose --no-actions
echo "✅ Update command executed successfully" >> $GITHUB_STEP_SUMMARY
build:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-build
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Node.js
id: setup-node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: "24"
cache: npm
cache-dependency-path: actions/setup/js/package-lock.json
- name: Report Node cache status
run: |
if [ "${{ steps.setup-node.outputs.cache-hit }}" == "true" ]; then
echo "✅ Node cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Node cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: npm ci
run: npm ci
working-directory: ./actions/setup/js
- name: Build code
run: make build
- name: Rebuild lock files
run: make recompile
env:
GH_TOKEN: ${{ github.token }}
js:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-js
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Node.js
id: setup-node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: "24"
cache: npm
cache-dependency-path: actions/setup/js/package-lock.json
- name: Report Node cache status
run: |
if [ "${{ steps.setup-node.outputs.cache-hit }}" == "true" ]; then
echo "✅ Node cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Node cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Install npm dependencies
run: cd actions/setup/js && npm ci
- name: Run tests
run: cd actions/setup/js && npm test
bench:
# Only run benchmarks on main branch for performance tracking
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-bench
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Verify dependencies
run: go mod verify
- name: Run benchmarks
run: make bench
- name: Display benchmark summary
run: |
echo "## 📊 Benchmark Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
# Show compiler benchmarks from the results
grep "BenchmarkCompile" bench_results.txt | head -20 >> $GITHUB_STEP_SUMMARY || echo "No benchmark results found" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📁 Full results saved to artifact: benchmark-results" >> $GITHUB_STEP_SUMMARY
# Benchmark results for performance trend analysis - 14 days allows comparison across multiple runs
- name: Save benchmark results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: benchmark-results
path: bench_results.txt
if-no-files-found: ignore
retention-days: 14
lint-go:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-lint-go
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
# Go formatting check (fast, no deps needed)
- name: Check Go formatting
run: |
unformatted=$(go fmt ./...)
if [ -n "$unformatted" ]; then
echo "❌ Code is not formatted. Run 'make fmt' to fix." >> $GITHUB_STEP_SUMMARY
echo "Unformatted files:" >> $GITHUB_STEP_SUMMARY
echo "$unformatted" >> $GITHUB_STEP_SUMMARY
echo ""
echo "To fix this locally, run:"
echo " make fmt"
echo ""
echo "Or format individual files with:"
echo " go fmt ./path/to/file.go"
exit 1
fi
echo "✅ Go formatting check passed" >> $GITHUB_STEP_SUMMARY
# Install only golangci-lint (the only tool needed for linting)
# Other tools (actionlint, gosec, gopls, govulncheck) are not used in this job
- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/v2/cmd/[email protected]
# Run golangci-lint via Makefile for consistency
# Uses incremental linting on PRs for faster CI (50-75% speedup)
# Performance optimizations in .golangci.yml:
# - timeout: 5m prevents hanging
# - modules-download-mode: readonly uses cached modules only
- name: Run golangci-lint
run: |
export PATH="$PATH:$(go env GOPATH)/bin"
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Incremental linting on PRs - only check changed files
# This provides 50-75% faster linting on typical PRs
make golint-incremental BASE_REF=origin/${{ github.base_ref }}
else
# Full scan on main branch to ensure comprehensive coverage
make golint
fi
# Error message linting (requires Go only)
- name: Lint error messages
run: make lint-errors
lint-js:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-lint-js
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Node.js
id: setup-node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: "24"
cache: npm
cache-dependency-path: actions/setup/js/package-lock.json
- name: Report Node cache status
run: |
if [ "${{ steps.setup-node.outputs.cache-hit }}" == "true" ]; then
echo "✅ Node cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Node cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Install npm dependencies
run: cd actions/setup/js && npm ci
# JavaScript and JSON formatting checks
- name: Lint JavaScript files
run: make lint-cjs
- name: Check JSON formatting
run: make fmt-check-json
audit:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-audit
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Verify dependencies
run: go mod verify
- name: Build gh-aw binary
run: make build
- name: Run dependency audit (human-readable)
run: |
echo "## Dependency Health Audit" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
./gh-aw update --audit 2>&1 | tee audit_output.txt || true
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
head -100 audit_output.txt >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
- name: Run dependency audit (JSON)
id: audit_json
run: |
# Run audit with JSON output for agent-friendly parsing
./gh-aw update --audit --json > audit.json 2>&1
# Display summary in GitHub Actions
echo "✅ Dependency audit completed" >> $GITHUB_STEP_SUMMARY
# Extract key metrics
TOTAL_DEPS=$(jq '.summary.total_dependencies' audit.json)
OUTDATED=$(jq '.summary.outdated_count' audit.json)
SECURITY=$(jq '.summary.security_advisories' audit.json)
V0_PERCENT=$(jq '.summary.v0_percentage' audit.json)
echo "📊 **Audit Results:**" >> $GITHUB_STEP_SUMMARY
echo "- Total dependencies: $TOTAL_DEPS" >> $GITHUB_STEP_SUMMARY
echo "- Outdated: $OUTDATED" >> $GITHUB_STEP_SUMMARY
echo "- Security advisories: $SECURITY" >> $GITHUB_STEP_SUMMARY
echo "- v0.x exposure: ${V0_PERCENT}%" >> $GITHUB_STEP_SUMMARY
- name: Upload audit results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dependency-audit
path: |
audit.json
audit_output.txt
retention-days: 30
actions-build:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-actions-build
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Verify dependencies
run: go mod verify
- name: Build actions
run: make actions-build
- name: Validate actions
run: make actions-validate
fuzz:
# Only run fuzz tests on main branch (10s is insufficient for PRs)
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-fuzz
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Verify dependencies
run: go mod verify
- name: Run fuzz tests
run: |
go test -run='^$' -fuzz=FuzzParseFrontmatter -fuzztime=10s ./pkg/parser/
go test -run='^$' -fuzz=FuzzScheduleParser -fuzztime=10s ./pkg/parser/
go test -run='^$' -fuzz=FuzzExpressionParser -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzMentionsFiltering -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzSanitizeOutput -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzSanitizeIncomingText -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzSanitizeLabelContent -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzWrapExpressionsInTemplateConditionals -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzYAMLParsing -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzTemplateRendering -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzInputValidation -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzNetworkPermissions -fuzztime=10s ./pkg/workflow/
go test -run='^$' -fuzz=FuzzSafeJobConfig -fuzztime=10s ./pkg/workflow/
security:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-security
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Verify dependencies
run: go mod verify
- name: Run security regression tests
run: make test-security
security-scan:
# Only run security scans on main branch to reduce PR overhead
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10 # Prevent jobs from hanging indefinitely
permissions:
contents: read
strategy:
fail-fast: false
matrix:
tool:
- name: zizmor
flag: --zizmor
- name: actionlint
flag: --actionlint
- name: poutine
flag: --poutine
concurrency:
group: ci-${{ github.ref }}-security-scan-${{ matrix.tool.name }}
cancel-in-progress: true
name: "Security Scan: ${{ matrix.tool.name }}"
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Build gh-aw
run: make build
- name: Run ${{ matrix.tool.name }} security scan on poem workflow
run: ./gh-aw compile poem-bot ${{ matrix.tool.flag }} --verbose
logs-token-check:
if: false
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
concurrency:
group: ci-${{ github.ref }}-logs-token-check
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Build gh-aw
run: make build
- name: Run logs command with JSON output
id: logs_check
run: |
set -e # Fail on first error
# Run the logs command and capture only stdout (JSON output)
# stderr is not redirected, so warning messages go to console
./gh-aw logs smoke-copilot -c 2 --json --verbose > logs_output.json
# Display the output for debugging
echo "Logs command output:"
cat logs_output.json
# Check if the JSON structure is valid
echo "## Validating JSON Structure"
# Check if token count is found in the JSON output
if jq -e '.summary.total_tokens' logs_output.json > /dev/null 2>&1; then
TOKEN_COUNT=$(jq '.summary.total_tokens' logs_output.json)
echo "✅ Token count found: $TOKEN_COUNT"
# Validate that token count is greater than 0
if [ "$TOKEN_COUNT" -gt 0 ]; then
echo "✅ Token count is greater than 0: $TOKEN_COUNT"
echo "token_count=$TOKEN_COUNT" >> $GITHUB_OUTPUT
else
echo "❌ Token count is 0 - expected tokens to be parsed from logs"
exit 1
fi
else
echo "❌ Token count not found in JSON output"
exit 1
fi
# Check if runs array exists (even if empty)
if jq -e '.runs' logs_output.json > /dev/null 2>&1; then
RUNS_COUNT=$(jq '.runs | length' logs_output.json)
echo "✅ Runs array found: $RUNS_COUNT runs"
else
echo "❌ Runs array not found in JSON output"
exit 1
fi
# If there are runs, validate that key fields are resolved
if [ "$RUNS_COUNT" -gt 0 ]; then
# Check if agent (engine_id) field exists in first run
if jq -e '.runs[0] | has("agent")' logs_output.json > /dev/null 2>&1; then
AGENT=$(jq -r '.runs[0].agent // "null"' logs_output.json)
echo "✅ Agent field found in run: $AGENT"
else
echo "❌ Agent field not found in run data"
exit 1
fi
# Check if workflow_path field exists in first run
if jq -e '.runs[0] | has("workflow_path")' logs_output.json > /dev/null 2>&1; then
WORKFLOW_PATH=$(jq -r '.runs[0].workflow_path // "null"' logs_output.json)
echo "✅ Workflow path field found in run: $WORKFLOW_PATH"
else
echo "❌ Workflow path field not found in run data"
exit 1
fi
# Check if workflow_name is present
if jq -e '.runs[0].workflow_name' logs_output.json > /dev/null 2>&1; then
WORKFLOW_NAME=$(jq -r '.runs[0].workflow_name' logs_output.json)
echo "✅ Workflow name found in run: $WORKFLOW_NAME"
else
echo "❌ Workflow name not found in run data"
exit 1
fi
else
echo "ℹ️ No runs found to validate (this is ok)"
fi
echo "✅ All JSON structure validations passed"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
mcp-server-compile-test:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-mcp-server-compile-test
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Build gh-aw binary
run: make build
- name: Create test workflow with error
run: |
mkdir -p .github/workflows
cat > .github/workflows/test-invalid.md << 'EOF'
---
on: push
engine: copilot
invalid_field: this will cause an error
---
# Test Invalid Workflow
This workflow has an invalid field that will cause a compilation error.
EOF
- name: Test MCP server compile tool
run: |
# Create a test script using the MCP Go SDK
cat > test_mcp_compile.go << 'GOEOF'
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"time"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
// Create MCP client
client := mcp.NewClient(&mcp.Implementation{
Name: "ci-test-client",
Version: "1.0.0",
}, nil)
// Start the MCP server as a subprocess with absolute path
binaryPath := "./gh-aw"
serverCmd := exec.Command(binaryPath, "mcp-server", "--cmd", binaryPath)
transport := &mcp.CommandTransport{Command: serverCmd}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Connect to the server
session, err := client.Connect(ctx, transport, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to MCP server: %v\n", err)
os.Exit(1)
}
defer session.Close()
fmt.Println("✅ Successfully connected to MCP server")
// Call the compile tool with the invalid workflow
params := &mcp.CallToolParams{
Name: "compile",
Arguments: map[string]any{
"workflows": []string{"test-invalid.md"},
},
}
result, err := session.CallTool(ctx, params)
if err != nil {
fmt.Fprintf(os.Stderr, "MCP tool call returned error (this is expected): %v\n", err)
// Check if the error contains expected error information
fmt.Println("✅ Compile tool correctly returned an error")
os.Exit(0)
}
// Get the result content
if len(result.Content) == 0 {
fmt.Fprintln(os.Stderr, "❌ Expected non-empty result from compile tool")
os.Exit(1)
}
textContent, ok := result.Content[0].(*mcp.TextContent)
if !ok {
fmt.Fprintln(os.Stderr, "❌ Expected text content from compile tool")
os.Exit(1)
}
fmt.Printf("Compile tool output:\n%s\n", textContent.Text)
// Parse the JSON output to check for errors
var compileResults []map[string]any
if err := json.Unmarshal([]byte(textContent.Text), &compileResults); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse JSON output: %v\n", err)
os.Exit(1)
}
// Check if the workflow is marked as invalid
if len(compileResults) == 0 {
fmt.Fprintln(os.Stderr, "❌ Expected at least one workflow result")
os.Exit(1)
}
result0 := compileResults[0]
valid, ok := result0["valid"].(bool)
if !ok {
fmt.Fprintln(os.Stderr, "❌ Expected 'valid' field in result")
os.Exit(1)
}
if valid {
fmt.Fprintln(os.Stderr, "❌ Expected workflow to be invalid")
os.Exit(1)
}
// Check that errors field exists and has at least one error
errors, ok := result0["errors"].([]any)
if !ok || len(errors) == 0 {
fmt.Fprintln(os.Stderr, "❌ Expected errors array with at least one error")
os.Exit(1)
}
fmt.Println("✅ Compile tool correctly reported validation errors:")
errorsJSON, _ := json.MarshalIndent(errors, " ", " ")
fmt.Printf(" %s\n", string(errorsJSON))
os.Exit(0)
}
GOEOF
# Run the test
go run test_mcp_compile.go
- name: Report test results
if: always()
run: |
echo "## MCP Server Compile Tool Test" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "This test verifies that:" >> $GITHUB_STEP_SUMMARY
echo "1. The gh-aw MCP server can be started successfully" >> $GITHUB_STEP_SUMMARY
echo "2. The compile tool can be invoked through the MCP server" >> $GITHUB_STEP_SUMMARY
echo "3. The compile tool correctly detects and reports validation errors" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Test completed successfully" >> $GITHUB_STEP_SUMMARY