diff --git a/.github/workflows/contributor-ci.yml b/.github/workflows/contributor-ci.yml new file mode 100644 index 0000000..cb60f89 --- /dev/null +++ b/.github/workflows/contributor-ci.yml @@ -0,0 +1,57 @@ +name: Contributor CI + +# Runs on fork's compute allowance (Microsoft/GitHub free tier) +# Heavy analysis happens here, not on upstream + +on: + push: + branches-ignore: [main, dev] # Only feature branches (forks) + pull_request: + branches: [main, dev] + +jobs: + # This runs on CONTRIBUTOR'S allowance + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run analysis + run: | + echo "## 🔍 Contributor Analysis" >> $GITHUB_STEP_SUMMARY + echo "This CI runs on your fork's GitHub Actions allowance." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Add your heavy analysis here - it's FREE for contributors + # - CodeQL scanning + # - Dependency analysis + # - AI-powered code review (if using Copilot) + # - Full test suite + + - name: Lint + run: | + # Linting on contributor's compute + echo "Running lint..." + + - name: Security scan + run: | + # Security scanning on contributor's compute + echo "Running security scan..." + + # AI-powered analysis (uses contributor's Copilot/AI allowance) + ai-review: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + + - name: AI Analysis Placeholder + run: | + echo "## 🤖 AI Analysis" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "This would run AI analysis using:" >> $GITHUB_STEP_SUMMARY + echo "- Contributor's Copilot allowance" >> $GITHUB_STEP_SUMMARY + echo "- GitHub's free AI features" >> $GITHUB_STEP_SUMMARY + echo "- Any AI APIs the contributor has configured" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Upstream repo pays nothing. 💰" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/fork-ai-triage.yml b/.github/workflows/fork-ai-triage.yml new file mode 100644 index 0000000..4fc72ac --- /dev/null +++ b/.github/workflows/fork-ai-triage.yml @@ -0,0 +1,120 @@ +name: AI Triage (Fork Compute) + +# Strategy: Heavy AI analysis runs on contributor's fork +# They get free GitHub Copilot / Actions minutes +# We only do lightweight verification + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + +jobs: + # Runs on contributor's GitHub Actions allowance + ai-triage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed files + id: changes + run: | + FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | head -50) + echo "files<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "count=$(echo "$FILES" | wc -l)" >> $GITHUB_OUTPUT + + - name: Categorize changes + id: categorize + run: | + # Determine what type of changes (runs on fork's compute) + PHP_COUNT=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -c '\.php$' || echo 0) + GO_COUNT=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -c '\.go$' || echo 0) + JS_COUNT=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -cE '\.(js|ts)$' || echo 0) + DOC_COUNT=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -cE '\.(md|txt)$' || echo 0) + + echo "php=$PHP_COUNT" >> $GITHUB_OUTPUT + echo "go=$GO_COUNT" >> $GITHUB_OUTPUT + echo "js=$JS_COUNT" >> $GITHUB_OUTPUT + echo "docs=$DOC_COUNT" >> $GITHUB_OUTPUT + + # Suggest labels + LABELS="" + [ "$PHP_COUNT" -gt 0 ] && LABELS="$LABELS,lang:php" + [ "$GO_COUNT" -gt 0 ] && LABELS="$LABELS,lang:go" + [ "$JS_COUNT" -gt 0 ] && LABELS="$LABELS,lang:js" + [ "$DOC_COUNT" -gt 0 ] && LABELS="$LABELS,type:docs" + + echo "labels=${LABELS#,}" >> $GITHUB_OUTPUT + + - name: Security quick scan + run: | + # Quick security checks (contributor's compute) + echo "Checking for secrets..." + + # Check for common secret patterns + if git diff origin/${{ github.base_ref }}...HEAD | grep -iE '(api_key|secret|password|token)\s*[:=]' | grep -v 'example\|placeholder\|xxx'; then + echo "::warning::Possible secrets detected in diff" + fi + + # Check for .env files + if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '\.env$'; then + echo "::error::Environment file changes detected - review carefully" + fi + + - name: Generate triage summary + uses: actions/github-script@v7 + with: + script: | + const changes = `${{ steps.changes.outputs.files }}`.split('\n').filter(f => f); + const labels = '${{ steps.categorize.outputs.labels }}'.split(',').filter(l => l); + + const summary = `## 🤖 AI Triage Summary + + **Files changed:** ${changes.length} + **Languages detected:** ${labels.filter(l => l.startsWith('lang:')).map(l => l.replace('lang:', '')).join(', ') || 'none'} + + ### Suggested labels + ${labels.map(l => '`' + l + '`').join(' ') || '_No suggestions_'} + + ### Changed files +
+ View ${changes.length} files + + \`\`\` + ${changes.join('\n')} + \`\`\` +
+ + --- + _This analysis ran on the contributor's GitHub Actions allowance._ 🆓 + `; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: summary + }); + + - name: Apply suggested labels + if: steps.categorize.outputs.labels != '' + uses: actions/github-script@v7 + continue-on-error: true + with: + script: | + const labels = '${{ steps.categorize.outputs.labels }}'.split(',').filter(l => l); + if (labels.length > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labels + }); + } diff --git a/.github/workflows/fork-pr-analysis.yml b/.github/workflows/fork-pr-analysis.yml new file mode 100644 index 0000000..cc8ae10 --- /dev/null +++ b/.github/workflows/fork-pr-analysis.yml @@ -0,0 +1,37 @@ +name: PR Analysis (Fork) + +# This workflow runs on the FORK's resources, not ours +# Contributors get free GitHub Actions minutes +# Microsoft/GitHub subsidizes the compute + +on: + pull_request_target: + types: [opened, synchronize] + +jobs: + # Lightweight check on our side - just verify the fork did the work + verify-fork-ci: + runs-on: ubuntu-latest + steps: + - name: Check fork CI status + uses: actions/github-script@v7 + with: + script: | + const { data: checks } = await github.rest.checks.listForRef({ + owner: context.payload.pull_request.head.repo.owner.login, + repo: context.payload.pull_request.head.repo.name, + ref: context.payload.pull_request.head.sha + }); + + const passed = checks.check_runs.filter(c => c.conclusion === 'success'); + const failed = checks.check_runs.filter(c => c.conclusion === 'failure'); + + console.log(`Fork CI: ${passed.length} passed, ${failed.length} failed`); + + if (failed.length > 0) { + core.setFailed('Fork CI has failures - contributor must fix on their fork'); + } + +--- +# This file goes in the TEMPLATE so forks inherit it +# The heavy work runs on contributor's GitHub Actions allowance