From 82e567105a3643d5d007077302d192535087ddca Mon Sep 17 00:00:00 2001 From: jbdevprimary Date: Thu, 8 Jan 2026 06:10:46 +0000 Subject: [PATCH] chore(sync): [skip actions] synced local '.github/' with remote 'sync-files/always-sync/global/.github/' Synced from jbcom/control-center Run: https://github.com/jbcom/control-center/actions/runs/20807422644 [skip actions] --- .github/settings.yml | 9 - .github/workflows/ai-curator.yml | 204 ---------- .github/workflows/ai-delegator.yml | 111 ----- .github/workflows/ai-fixer.yml | 354 ---------------- .github/workflows/ai-reviewer.yml | 197 --------- .github/workflows/autoheal.yml | 142 +++++++ .github/workflows/delegator.yml | 90 ++++ .github/workflows/ecosystem-agents.yml | 352 ---------------- .github/workflows/ecosystem-assessment.yml | 221 ---------- .github/workflows/ecosystem-control.yml | 384 ------------------ .github/workflows/ecosystem-delegator.yml | 225 ---------- .github/workflows/ecosystem-fixer.yml | 233 ----------- .github/workflows/ecosystem-merge.yml | 341 ---------------- .github/workflows/ecosystem-reviewer.yml | 252 ------------ .github/workflows/ecosystem-surveyor.yml | 248 ----------- .github/workflows/ecosystem-triage.yml | 283 ------------- .../workflows/jules-completion-handler.yml | 112 ----- .github/workflows/jules-supervisor.yml | 31 -- .github/workflows/review.yml | 95 +++++ .github/workflows/triage.yml | 350 ++++++++++++++++ 20 files changed, 677 insertions(+), 3557 deletions(-) delete mode 100644 .github/settings.yml delete mode 100644 .github/workflows/ai-curator.yml delete mode 100644 .github/workflows/ai-delegator.yml delete mode 100644 .github/workflows/ai-fixer.yml delete mode 100644 .github/workflows/ai-reviewer.yml create mode 100644 .github/workflows/autoheal.yml create mode 100644 .github/workflows/delegator.yml delete mode 100644 .github/workflows/ecosystem-agents.yml delete mode 100644 .github/workflows/ecosystem-assessment.yml delete mode 100644 .github/workflows/ecosystem-control.yml delete mode 100644 .github/workflows/ecosystem-delegator.yml delete mode 100644 .github/workflows/ecosystem-fixer.yml delete mode 100644 .github/workflows/ecosystem-merge.yml delete mode 100644 .github/workflows/ecosystem-reviewer.yml delete mode 100644 .github/workflows/ecosystem-surveyor.yml delete mode 100644 .github/workflows/ecosystem-triage.yml delete mode 100644 .github/workflows/jules-completion-handler.yml delete mode 100644 .github/workflows/jules-supervisor.yml create mode 100644 .github/workflows/review.yml create mode 100644 .github/workflows/triage.yml diff --git a/.github/settings.yml b/.github/settings.yml deleted file mode 100644 index 781a146..0000000 --- a/.github/settings.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Organization .github repository settings -# This is the base configuration for all jbcom repositories -# Other repositories extend this using: _extends: .github - -# Note: This repository itself uses organization defaults -# No _extends needed (this IS the source of truth) - -# Control repositories use organization defaults -# No language-specific linters required diff --git a/.github/workflows/ai-curator.yml b/.github/workflows/ai-curator.yml deleted file mode 100644 index 5277cff..0000000 --- a/.github/workflows/ai-curator.yml +++ /dev/null @@ -1,204 +0,0 @@ -# AI Curator - Daily/event-driven curation pipeline -# Tier 1: Ollama (fast triage) via control-center -# Tier 2: Claude (issue analysis, deduplication) -# Tier 3: Jules (scheduled maintenance tasks) -# Synced from jbcom/control-center -name: AI Curator - -on: - issues: - types: [opened, labeled] - schedule: - - cron: '0 3 * * *' # 3 AM UTC daily - workflow_dispatch: - inputs: - mode: - description: 'Curation mode' - type: choice - options: - - full - - triage-only - default: full - -# No workflow-level permissions - job level only -permissions: {} - -jobs: - # ════════════════════════════════════════════════════════════════ - # EVENT: New Issue Triage (Claude) - # ════════════════════════════════════════════════════════════════ - claude-triage: - name: Claude Issue Triage - if: github.event_name == 'issues' && github.event.action == 'opened' - runs-on: ubuntu-latest - timeout-minutes: 10 - concurrency: - group: claude-triage-${{ github.event.issue.number }} - cancel-in-progress: true - permissions: - contents: read - issues: write - id-token: write - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "::warning::ANTHROPIC_API_KEY not configured" - echo "available=false" >> $GITHUB_OUTPUT - fi - - - name: Checkout - if: steps.check-key.outputs.available == 'true' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 1 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Triage with Claude - if: steps.check-key.outputs.available == 'true' - uses: anthropics/claude-code-action@7145c3e0510bcdbdd29f67cc4a8c1958f1acfa2f # v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "cursor[bot],jules[bot],google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - prompt: | - Triage issue #${{ github.event.issue.number }} in ${{ github.repository }}. - - 1. Add appropriate labels (bug/enhancement/docs, priority) - 2. Check for duplicates - link and close if duplicate - 3. Add a helpful acknowledgment comment - - claude_args: | - --allowedTools "Read,Glob,Grep,LS,Bash(gh:*),mcp__github__get_issue,mcp__github__update_issue,mcp__github__add_issue_comment,mcp__github__list_issues,mcp__github__search_issues" - - # ════════════════════════════════════════════════════════════════ - # SCHEDULED: Ollama Curation (control-center) - # ════════════════════════════════════════════════════════════════ - build: - name: Build Control Center - if: | - github.event_name == 'schedule' || - (github.event_name == 'workflow_dispatch' && inputs.mode != 'triage-only') - permissions: - contents: read - uses: jbcom/control-center/.github/workflows/control-center-build.yml@main - - ollama-curate: - name: Ollama Curation - needs: build - runs-on: ubuntu-latest - permissions: - contents: read - issues: write - pull-requests: write - steps: - - name: Download binary - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: control-center-binary - path: /tmp - run-id: ${{ github.run_id }} - github-token: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - - name: Run curator - env: - GITHUB_TOKEN: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} - GOOGLE_JULES_API_KEY: ${{ secrets.GOOGLE_JULES_API_KEY }} - CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} - run: | - chmod +x /tmp/control-center - /tmp/control-center curator \ - --repo "${{ github.repository }}" \ - --log-level info - - # ════════════════════════════════════════════════════════════════ - # SCHEDULED: Claude Issue Deduplication - # ════════════════════════════════════════════════════════════════ - claude-dedup: - name: Claude Deduplication - if: | - github.event_name == 'schedule' || - github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - timeout-minutes: 10 - concurrency: - group: claude-dedup - cancel-in-progress: true - permissions: - contents: read - issues: write - id-token: write - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "available=false" >> $GITHUB_OUTPUT - fi - - - name: Checkout - if: steps.check-key.outputs.available == 'true' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 1 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Dedup with Claude - if: steps.check-key.outputs.available == 'true' - uses: anthropics/claude-code-action@7145c3e0510bcdbdd29f67cc4a8c1958f1acfa2f # v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "cursor[bot],jules[bot],google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - prompt: | - Scan ${{ github.repository }} for duplicate issues. - - Find similar issues, link duplicates to the canonical issue, and close them. - - claude_args: | - --allowedTools "Read,Glob,Grep,LS,Bash(gh:*),mcp__github__list_issues,mcp__github__search_issues,mcp__github__get_issue,mcp__github__update_issue,mcp__github__add_issue_comment" - - # ════════════════════════════════════════════════════════════════ - # EVENT: Jules Bug Fixer (on 'bug' label) - # ════════════════════════════════════════════════════════════════ - jules-bug-fix: - name: Jules Bug Fixer - if: | - github.event_name == 'issues' && - github.event.action == 'labeled' && - github.event.label.name == 'bug' - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: read - issues: read - id-token: write - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.GOOGLE_JULES_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "available=false" >> $GITHUB_OUTPUT - fi - - - name: Fix Bug with Jules - if: steps.check-key.outputs.available == 'true' - uses: google-labs-code/jules-action@bff7875eaa123cac6742b7cfc51005b95ba4d566 # v1.0.0 - with: - jules_api_key: ${{ secrets.GOOGLE_JULES_API_KEY }} - starting_branch: 'main' - prompt: | - Fix issue #${{ github.event.issue.number }}: ${{ github.event.issue.title }} - - ${{ github.event.issue.body }} - - Create a PR on branch `jules/fix-issue-${{ github.event.issue.number }}` with labels `jules-pr`, `bug-fix`. - PR body must include: Fixes #${{ github.event.issue.number }}, root cause, and solution. diff --git a/.github/workflows/ai-delegator.yml b/.github/workflows/ai-delegator.yml deleted file mode 100644 index 7049642..0000000 --- a/.github/workflows/ai-delegator.yml +++ /dev/null @@ -1,111 +0,0 @@ -# AI Delegator - Routes AI agent requests from comments -# Supports: @claude, /jules, /cursor -name: AI Delegator - -on: - issue_comment: - types: [created] - -# No workflow-level permissions - job level only -permissions: {} - -jobs: - # ════════════════════════════════════════════════════════════════ - # CLAUDE: @claude mentions in issues/PRs - # ════════════════════════════════════════════════════════════════ - claude: - name: Claude Agent - if: | - contains(github.event.comment.body, '@claude') - runs-on: ubuntu-latest - timeout-minutes: 30 - concurrency: - group: claude-${{ github.event.issue.number || github.event.pull_request.number }} - cancel-in-progress: false - permissions: - contents: write - pull-requests: write - issues: write - id-token: write - actions: read - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "::warning::ANTHROPIC_API_KEY not configured" - echo "available=false" >> $GITHUB_OUTPUT - fi - - - name: Checkout - if: steps.check-key.outputs.available == 'true' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 1 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Claude Code Action - if: steps.check-key.outputs.available == 'true' - uses: anthropics/claude-code-action@7145c3e0510bcdbdd29f67cc4a8c1958f1acfa2f # v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "cursor[bot],jules[bot],google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - additional_permissions: | - actions: read - track_progress: true - prompt: | - You are helping with ${{ github.repository }}. - - Context: ${{ github.event.issue.pull_request && 'Pull Request' || 'Issue' }} #${{ github.event.issue.number }} - - First, read CLAUDE.md, AGENTS.md, or README.md to understand the project. - Then address the user's request in their comment. - - claude_args: | - --allowedTools "Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(pnpm:*),Bash(npm:*),Bash(npx:*),Bash(bun:*),Bash(gh:*),mcp__github__get_issue,mcp__github__update_issue,mcp__github__add_issue_comment,mcp__github__list_issues,mcp__github__search_issues,mcp__github__get_pull_request,mcp__github__get_pull_request_diff,mcp__github__list_pull_request_commits,mcp__github__create_pull_request_review,mcp__github__add_pull_request_comment,mcp__github__create_pull_request,mcp__github__list_pull_requests,mcp__github__get_file_contents,mcp__github__list_files,mcp__github__create_or_update_file,mcp__github_inline_comment__create_inline_comment,mcp__github_ci__get_ci_status,mcp__github_ci__get_workflow_run_details,mcp__github_ci__download_job_log,mcp__github_ci__list_workflow_runs" - - # ════════════════════════════════════════════════════════════════ - # CONTROL-CENTER: /jules, /cursor commands - # ════════════════════════════════════════════════════════════════ - build: - name: Build Control Center - if: | - github.event.issue.pull_request == null && - (contains(github.event.comment.body, '/jules') || contains(github.event.comment.body, '/cursor')) - permissions: - contents: read - uses: jbcom/control-center/.github/workflows/control-center-build.yml@main - - delegate: - name: Delegate to AI - needs: build - runs-on: ubuntu-latest - permissions: - contents: read - issues: write - pull-requests: write - steps: - - name: Download binary - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: control-center-binary - path: /tmp - run-id: ${{ github.run_id }} - github-token: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - - name: Run delegator - env: - GITHUB_TOKEN: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - GOOGLE_JULES_API_KEY: ${{ secrets.GOOGLE_JULES_API_KEY }} - CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} - COMMAND_BODY: ${{ github.event.comment.body }} - run: | - chmod +x /tmp/control-center - /tmp/control-center delegator \ - --repo "${{ github.repository }}" \ - --issue "${{ github.event.issue.number }}" \ - --command "$COMMAND_BODY" \ - --log-level info diff --git a/.github/workflows/ai-fixer.yml b/.github/workflows/ai-fixer.yml deleted file mode 100644 index ac967f2..0000000 --- a/.github/workflows/ai-fixer.yml +++ /dev/null @@ -1,354 +0,0 @@ -# AI CI Fixer - Tiered fix pipeline -# Tier 1: Ollama (fast diagnosis) via control-center -# Tier 2: Claude (complex fixes / flaky test detection) -# Tier 3: Jules (major refactor) when significant changes needed -name: AI Fixer - -on: - workflow_run: - workflows: ["CI", "Build", "Test", "Lint"] - types: [completed] - branches-ignore: [main] - -# No workflow-level permissions - job level only -permissions: {} - -jobs: - # ════════════════════════════════════════════════════════════════ - # VALIDATION: Security checks before processing - # ════════════════════════════════════════════════════════════════ - validate: - name: Validate Source - if: | - github.event.workflow_run.conclusion == 'failure' && - github.event.workflow_run.pull_requests[0] && - !startsWith(github.event.workflow_run.head_branch, 'claude-fix-') && - !startsWith(github.event.workflow_run.head_branch, 'jules/') - runs-on: ubuntu-latest - permissions: - pull-requests: read - outputs: - is_trusted: ${{ steps.check.outputs.is_trusted }} - head_branch: ${{ steps.check.outputs.head_branch }} - pr_number: ${{ steps.check.outputs.pr_number }} - steps: - - name: Security validation - id: check - env: - HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} - HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }} - BASE_REPO: ${{ github.event.workflow_run.repository.full_name }} - PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} - run: | - # Security: Reject PRs from forks - if [[ "$HEAD_REPO" != "$BASE_REPO" ]]; then - echo "::warning::PR from fork ($HEAD_REPO), skipping auto-fix" - echo "is_trusted=false" >> $GITHUB_OUTPUT - exit 0 - fi - - # Validate branch name - if [[ ! "$HEAD_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then - echo "::error::Invalid branch name" - echo "is_trusted=false" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "is_trusted=true" >> $GITHUB_OUTPUT - echo "head_branch=$HEAD_BRANCH" >> $GITHUB_OUTPUT - echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT - - # ════════════════════════════════════════════════════════════════ - # TIER 1: Ollama Quick Fix (control-center) - # ════════════════════════════════════════════════════════════════ - build: - needs: validate - if: needs.validate.outputs.is_trusted == 'true' - permissions: - contents: read - uses: jbcom/control-center/.github/workflows/control-center-build.yml@main - - ollama-fix: - name: Ollama Fix - needs: [validate, build] - if: needs.validate.outputs.is_trusted == 'true' - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - actions: read - outputs: - fix_applied: ${{ steps.fix.outputs.fix_applied }} - needs_escalation: ${{ steps.fix.outputs.needs_escalation }} - steps: - - name: Download binary - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: control-center-binary - path: /tmp - run-id: ${{ github.run_id }} - github-token: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - - name: Run fixer - id: fix - env: - GITHUB_TOKEN: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} - run: | - chmod +x /tmp/control-center - - OUTPUT=$(/tmp/control-center fixer \ - --repo "${{ github.repository }}" \ - --run-id "${{ github.event.workflow_run.id }}" \ - --log-level info 2>&1) || true - - # Check if fix was applied - if echo "$OUTPUT" | grep -q "fix applied\|commit created\|PR created"; then - echo "fix_applied=true" >> $GITHUB_OUTPUT - echo "needs_escalation=false" >> $GITHUB_OUTPUT - else - echo "fix_applied=false" >> $GITHUB_OUTPUT - echo "needs_escalation=true" >> $GITHUB_OUTPUT - fi - - # ════════════════════════════════════════════════════════════════ - # TIER 1B: Claude Flaky Test Detection (parallel) - # ════════════════════════════════════════════════════════════════ - claude-flaky-detect: - name: Claude Flaky Detection - needs: validate - if: needs.validate.outputs.is_trusted == 'true' - runs-on: ubuntu-latest - timeout-minutes: 10 - concurrency: - group: claude-flaky-${{ github.event.workflow_run.id }} - cancel-in-progress: true - permissions: - contents: read - actions: write - pull-requests: write - id-token: write - outputs: - is_flaky: ${{ steps.detect.outputs.is_flaky }} - should_retry: ${{ steps.decide.outputs.should_retry }} - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "available=false" >> $GITHUB_OUTPUT - fi - - - name: Checkout - if: steps.check-key.outputs.available == 'true' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Detect flaky test failures - id: detect - if: steps.check-key.outputs.available == 'true' - uses: anthropics/claude-code-action@7145c3e0510bcdbdd29f67cc4a8c1958f1acfa2f # v1 - env: - WORKFLOW_URL: ${{ github.event.workflow_run.html_url }} - WORKFLOW_ID: ${{ github.event.workflow_run.id }} - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "cursor[bot],jules[bot],google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - prompt: | - The CI workflow failed: ${{ env.WORKFLOW_URL }} - Check the logs: gh run view ${{ env.WORKFLOW_ID }} --log-failed - - Determine if this looks like a flaky test failure by checking for: - - Timeout errors - - Race conditions - - Network errors - - "Expected X but got Y" intermittent failures - - Tests that passed in previous commits - - Return: - - is_flaky: true if likely flaky, false if real bug - - confidence: number 0-1 indicating confidence level - - summary: brief one-sentence explanation - claude_args: | - --json-schema '{"type":"object","properties":{"is_flaky":{"type":"boolean"},"confidence":{"type":"number","minimum":0,"maximum":1},"summary":{"type":"string"}},"required":["is_flaky","confidence","summary"]}' - --allowedTools "Read,Glob,Grep,LS,Bash(gh:*),mcp__github_ci__get_ci_status,mcp__github_ci__get_workflow_run_details,mcp__github_ci__download_job_log" - - - name: Decide on retry - id: decide - if: steps.check-key.outputs.available == 'true' - run: | - OUTPUT='${{ steps.detect.outputs.structured_output }}' - if ! echo "$OUTPUT" | jq empty 2>/dev/null; then - echo "should_retry=false" >> $GITHUB_OUTPUT - exit 0 - fi - IS_FLAKY=$(echo "$OUTPUT" | jq -r '.is_flaky // false') - CONFIDENCE=$(echo "$OUTPUT" | jq -r '.confidence // 0') - - # Auto-retry only if flaky AND high confidence - if [ "$IS_FLAKY" = "true" ] && [ "$(echo "$CONFIDENCE >= 0.7" | bc -l)" = "1" ]; then - echo "should_retry=true" >> $GITHUB_OUTPUT - echo "is_flaky=true" >> $GITHUB_OUTPUT - else - echo "should_retry=false" >> $GITHUB_OUTPUT - echo "is_flaky=$IS_FLAKY" >> $GITHUB_OUTPUT - fi - - - name: Retry workflow - if: steps.decide.outputs.should_retry == 'true' - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - HEAD_BRANCH: ${{ needs.validate.outputs.head_branch }} - WORKFLOW_NAME: ${{ github.event.workflow_run.name }} - run: | - echo "🔄 Flaky test detected - triggering automatic retry..." - gh workflow run "$WORKFLOW_NAME" --ref "$HEAD_BRANCH" - - # ════════════════════════════════════════════════════════════════ - # TIER 2: Claude Deep Fix (for complex failures) - # ════════════════════════════════════════════════════════════════ - claude-fix: - name: Claude Fix - needs: [validate, ollama-fix, claude-flaky-detect] - if: | - always() && - needs.validate.outputs.is_trusted == 'true' && - needs.ollama-fix.outputs.needs_escalation == 'true' && - needs.claude-flaky-detect.outputs.should_retry != 'true' - runs-on: ubuntu-latest - timeout-minutes: 30 - concurrency: - group: claude-fix-${{ github.event.workflow_run.id }} - cancel-in-progress: false - permissions: - contents: write - pull-requests: write - actions: read - issues: write - id-token: write - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "::warning::ANTHROPIC_API_KEY not configured" - echo "available=false" >> $GITHUB_OUTPUT - fi - - # Note: head_branch is validated in 'validate' job with regex ^[a-zA-Z0-9/_.-]+$ - # and fork PRs are rejected, making this safe for checkout - - name: Checkout code - if: steps.check-key.outputs.available == 'true' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - ref: ${{ needs.validate.outputs.head_branch }} - fetch-depth: 0 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Setup git identity - if: steps.check-key.outputs.available == 'true' - run: | - git config --global user.email "claude[bot]@users.noreply.github.com" - git config --global user.name "claude[bot]" - - - name: Get failure details - if: steps.check-key.outputs.available == 'true' - id: failure - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - # Get failed job logs - LOGS=$(gh run view ${{ github.event.workflow_run.id }} --log-failed 2>/dev/null | tail -200 || echo "Failed to fetch logs") - - # Save to file for Claude - echo "$LOGS" > /tmp/failure_logs.txt - echo "logs_file=/tmp/failure_logs.txt" >> $GITHUB_OUTPUT - - - name: Fix with Claude - if: steps.check-key.outputs.available == 'true' - uses: anthropics/claude-code-action@7145c3e0510bcdbdd29f67cc4a8c1958f1acfa2f # v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "cursor[bot],jules[bot],google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - additional_permissions: | - actions: read - prompt: | - Fix CI failure in ${{ github.repository }} PR #${{ needs.validate.outputs.pr_number }}. - - Failed workflow: ${{ github.event.workflow_run.name }} - Logs: /tmp/failure_logs.txt - - Diagnose the failure, implement a fix, and commit directly to this branch. - Verify your fix passes the build. - - claude_args: | - --allowedTools "Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(pnpm:*),Bash(npm:*),Bash(gh:*),mcp__github__get_pull_request,mcp__github__get_pull_request_diff,mcp__github_ci__get_ci_status,mcp__github_ci__download_job_log" - - # ════════════════════════════════════════════════════════════════ - # TIER 3: Jules Major Refactor (when fix requires significant changes) - # ════════════════════════════════════════════════════════════════ - jules-refactor: - name: Jules Refactor - needs: [validate, ollama-fix, claude-flaky-detect, claude-fix] - if: | - always() && - needs.validate.outputs.is_trusted == 'true' && - needs.claude-flaky-detect.outputs.should_retry != 'true' && - needs.claude-fix.result == 'failure' - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - pull-requests: write - id-token: write - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.GOOGLE_JULES_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "::warning::GOOGLE_JULES_API_KEY not configured" - echo "available=false" >> $GITHUB_OUTPUT - fi - - - name: Create Jules Session - if: steps.check-key.outputs.available == 'true' - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - GOOGLE_JULES_API_KEY: ${{ secrets.GOOGLE_JULES_API_KEY }} - REPO: ${{ github.repository }} - WORKFLOW_URL: ${{ github.event.workflow_run.html_url }} - PR_NUM: ${{ needs.validate.outputs.pr_number }} - HEAD_BRANCH: ${{ needs.validate.outputs.head_branch }} - run: | - # All user-controlled values passed via env vars (head_branch validated in 'validate' job) - RESPONSE=$(curl -sf -X POST "https://jules.googleapis.com/v1alpha/sessions" \ - -H "X-Goog-Api-Key: $GOOGLE_JULES_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg task "Fix CI failure in PR #${PR_NUM} - The automated fixers couldn't resolve this. Please analyze the failure at ${WORKFLOW_URL} and implement a comprehensive fix." \ - --arg repo "$REPO" \ - --arg branch "$HEAD_BRANCH" \ - '{ - prompt: $task, - sourceContext: { - source: ("sources/github/" + $repo), - githubRepoContext: { startingBranch: $branch } - }, - requirePlanApproval: false, - automationMode: "AUTO_CREATE_PR" - }')" || echo "{}") - - SESSION_ID=$(echo "$RESPONSE" | jq -r '.name // empty' | sed 's|sessions/||') - - if [ -n "$SESSION_ID" ]; then - gh pr comment "$PR_NUM" --body "🔧 **Jules major refactor session started**: https://jules.google.com/session/${SESSION_ID} - Both Ollama and Claude couldn't fix this CI failure. Jules will attempt a more comprehensive solution." - fi diff --git a/.github/workflows/ai-reviewer.yml b/.github/workflows/ai-reviewer.yml deleted file mode 100644 index 852a761..0000000 --- a/.github/workflows/ai-reviewer.yml +++ /dev/null @@ -1,197 +0,0 @@ -# AI Code Review - Tiered review pipeline -# Tier 1: Ollama (fast, cheap) via control-center -# Tier 2: Claude (escalation) for complex PRs -# Tier 3: Jules (refactor) when code changes needed -name: AI Review - -on: - pull_request: - types: [opened, synchronize, ready_for_review] - -# No workflow-level permissions - job level only -permissions: {} - -jobs: - # ════════════════════════════════════════════════════════════════ - # TIER 1: Ollama Quick Review (control-center) - # ════════════════════════════════════════════════════════════════ - build: - if: github.event.pull_request.draft == false - permissions: - contents: read - uses: jbcom/control-center/.github/workflows/control-center-build.yml@main - - ollama-review: - name: Ollama Review - needs: build - if: github.event.pull_request.draft == false - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - outputs: - suggestion_count: ${{ steps.review.outputs.suggestion_count }} - needs_escalation: ${{ steps.check.outputs.needs_escalation }} - steps: - - name: Download binary - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: control-center-binary - path: /tmp - run-id: ${{ github.run_id }} - github-token: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - - name: Run reviewer - id: review - env: - GITHUB_TOKEN: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} - run: | - chmod +x /tmp/control-center - OUTPUT=$(/tmp/control-center reviewer \ - --repo "${{ github.repository }}" \ - --pr "${{ github.event.pull_request.number }}" \ - --log-level info 2>&1) || true - - # Extract suggestion count from output - SUGGESTIONS=$(echo "$OUTPUT" | grep -oP 'suggestions?: \K\d+' || echo "0") - echo "suggestion_count=$SUGGESTIONS" >> $GITHUB_OUTPUT - - - name: Check if escalation needed - id: check - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - PR_NUM: ${{ github.event.pull_request.number }} - run: | - SUGGESTIONS="${{ steps.review.outputs.suggestion_count }}" - ADDITIONS=$(gh pr view "$PR_NUM" --json additions --jq '.additions' || echo "0") - - # Escalate if: >5 suggestions OR >500 lines changed - if [[ "${SUGGESTIONS:-0}" -gt 5 ]] || [[ "${ADDITIONS:-0}" -gt 500 ]]; then - echo "needs_escalation=true" >> $GITHUB_OUTPUT - else - echo "needs_escalation=false" >> $GITHUB_OUTPUT - fi - - # ════════════════════════════════════════════════════════════════ - # TIER 2: Claude Deep Review (escalation for complex PRs) - # ════════════════════════════════════════════════════════════════ - claude-review: - name: Claude Review - needs: ollama-review - if: | - always() && - needs.ollama-review.outputs.needs_escalation == 'true' && - needs.ollama-review.result != 'cancelled' - runs-on: ubuntu-latest - timeout-minutes: 20 - concurrency: - group: claude-review-${{ github.event.pull_request.number }} - cancel-in-progress: true - permissions: - contents: read - pull-requests: write - id-token: write - actions: read - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "::warning::ANTHROPIC_API_KEY not configured, skipping Claude review" - echo "available=false" >> $GITHUB_OUTPUT - fi - - - name: Checkout repository - if: steps.check-key.outputs.available == 'true' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 1 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Claude Code Review - if: steps.check-key.outputs.available == 'true' - uses: anthropics/claude-code-action@7145c3e0510bcdbdd29f67cc4a8c1958f1acfa2f # v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "cursor[bot],jules[bot],google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - additional_permissions: | - actions: read - prompt: | - Review PR #${{ github.event.pull_request.number }} in ${{ github.repository }}. - - This PR was escalated because it's large or complex. - - Review for: correctness, security, performance, and maintainability. - - Use inline comments for specific issues. Summarize in a PR comment. - - claude_args: | - --allowedTools "Read,Glob,Grep,LS,Bash(gh:*),mcp__github__get_pull_request,mcp__github__get_pull_request_diff,mcp__github__list_pull_request_commits,mcp__github__create_pull_request_review,mcp__github__get_file_contents,mcp__github_inline_comment__create_inline_comment,mcp__github_ci__get_ci_status" - - # ════════════════════════════════════════════════════════════════ - # TIER 3: Jules Refactor (when major code changes needed) - # ════════════════════════════════════════════════════════════════ - jules-refactor: - name: Jules Refactor - needs: [ollama-review, claude-review] - if: | - always() && - needs.ollama-review.result == 'success' && - needs.ollama-review.outputs.suggestion_count > 10 - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - pull-requests: write - id-token: write - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.GOOGLE_JULES_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "::warning::GOOGLE_JULES_API_KEY not configured, skipping Jules" - echo "available=false" >> $GITHUB_OUTPUT - fi - - - name: Create Jules Refactor Session - if: steps.check-key.outputs.available == 'true' - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - GOOGLE_JULES_API_KEY: ${{ secrets.GOOGLE_JULES_API_KEY }} - PR_NUM: ${{ github.event.pull_request.number }} - REPO: ${{ github.repository }} - SUGGESTION_COUNT: ${{ needs.ollama-review.outputs.suggestion_count }} - run: | - # Fetch PR data safely via gh CLI - PR_DATA=$(gh pr view "$PR_NUM" --json title,headRefName) - PR_TITLE=$(echo "$PR_DATA" | jq -r .title) - PR_BRANCH=$(echo "$PR_DATA" | jq -r .headRefName) - - RESPONSE=$(curl -sf -X POST "https://jules.googleapis.com/v1alpha/sessions" \ - -H "X-Goog-Api-Key: $GOOGLE_JULES_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg task "Refactor PR #${PR_NUM}: $PR_TITLE - Address the ${SUGGESTION_COUNT} review suggestions" \ - --arg repo "$REPO" \ - --arg branch "$PR_BRANCH" \ - '{ - prompt: $task, - sourceContext: { - source: ("sources/github/" + $repo), - githubRepoContext: { startingBranch: $branch } - }, - requirePlanApproval: false, - automationMode: "AUTO_CREATE_PR" - }')" || echo "{}") - - SESSION_ID=$(echo "$RESPONSE" | jq -r '.name // empty' | sed 's|sessions/||') - - if [ -n "$SESSION_ID" ]; then - gh pr comment "$PR_NUM" --body "🔧 **Jules refactoring session started**: https://jules.google.com/session/${SESSION_ID} - This PR has ${SUGGESTION_COUNT} suggestions. Jules will create a follow-up PR with improvements." - fi diff --git a/.github/workflows/autoheal.yml b/.github/workflows/autoheal.yml new file mode 100644 index 0000000..3d784f4 --- /dev/null +++ b/.github/workflows/autoheal.yml @@ -0,0 +1,142 @@ +name: Autoheal +# Consolidated CI failure resolution workflow +# Replaces: ai-fixer, ecosystem-fixer, ecosystem-fixer-local +# +# INTELLIGENT CONFLICT RESOLUTION: +# - Automatically resolves CI failures +# - Resolves merge conflicts triggered by triage workflow +# - Uses AI to analyze and fix issues + +on: + workflow_run: + workflows: ["CI", "Go"] + types: [completed] + workflow_call: + inputs: + operation: + description: 'Operation type (analyze_failure, resolve_conflicts)' + required: false + type: string + default: 'analyze_failure' + run_id: + description: 'Workflow run ID to fix' + required: false + type: string + pr_number: + description: 'PR number (for conflict resolution)' + required: false + type: string + repository: + description: 'Repository (owner/repo)' + required: true + type: string + workflow_dispatch: + inputs: + operation: + description: 'Operation to perform' + required: true + type: choice + options: + - analyze_failure + - resolve_conflicts + - apply_fix + run_id: + description: 'Workflow run ID to fix (for failures)' + required: false + type: string + pr_number: + description: 'PR number (for conflicts)' + required: false + type: string + repository: + description: 'Repository (owner/repo)' + required: false + type: string + +permissions: + contents: write + pull-requests: write + issues: write + actions: read + +jobs: + # ============================================ + # CI FAILURE RESOLUTION + # ============================================ + quick-fix: + runs-on: ubuntu-latest + if: | + (github.event.workflow_run.conclusion == 'failure') || + (github.event_name == 'workflow_call' && inputs.operation == 'analyze_failure') || + (github.event_name == 'workflow_dispatch' && inputs.operation == 'analyze_failure') + steps: + - name: Analyze failure with control-center + id: analyze + run: | + RUN_ID="${{ inputs.run_id || github.event.workflow_run.id }}" + REPO="${{ inputs.repository || github.repository }}" + + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + -e OLLAMA_API_KEY="${OLLAMA_API_KEY}" \ + jbcom/control-center:latest fixer analyze \ + --run-id "$RUN_ID" \ + --repo "$REPO" \ + --output json > fix-suggestion.json + + echo "suggestion<> $GITHUB_OUTPUT + cat fix-suggestion.json >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} + + - name: Apply quick fix + if: steps.analyze.outputs.suggestion != '' + run: | + docker run --rm \ + -v "${{ github.workspace }}:/workspace" \ + -w /workspace \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest fixer apply \ + --suggestion-file fix-suggestion.json \ + --repo ${{ inputs.repository || github.repository }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Comment fix on PR + if: github.event.workflow_run.pull_requests[0] + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const fs = require('fs'); + const suggestion = JSON.parse(fs.readFileSync('fix-suggestion.json', 'utf8')); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.workflow_run.pull_requests[0].number, + body: `## 🔧 CI Fix Suggestion\n\n${suggestion.description}\n\n---\n🤖 Generated by Control Center Autoheal` + }); + + # ============================================ + # MERGE CONFLICT RESOLUTION + # ============================================ + resolve-conflicts: + runs-on: ubuntu-latest + if: | + (github.event_name == 'workflow_call' && inputs.operation == 'resolve_conflicts') || + (github.event_name == 'workflow_dispatch' && inputs.operation == 'resolve_conflicts') + steps: + - name: Post conflict resolution guidance + run: | + PR_NUM="${{ inputs.pr_number }}" + REPO="${{ inputs.repository || github.repository }}" + + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest fixer resolve-conflict \ + --repo "$REPO" \ + --pr "$PR_NUM" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/delegator.yml b/.github/workflows/delegator.yml new file mode 100644 index 0000000..e5e47f6 --- /dev/null +++ b/.github/workflows/delegator.yml @@ -0,0 +1,90 @@ +name: Delegator +# Consolidated @claude command router +# Replaces: ai-delegator, ecosystem-delegator, ecosystem-delegator-local + +on: + issue_comment: + types: [created] + workflow_call: + inputs: + comment_id: + description: 'Comment ID containing @claude command' + required: true + type: string + repository: + description: 'Repository (owner/repo)' + required: true + type: string + workflow_dispatch: + inputs: + issue_number: + description: 'Issue/PR number' + required: true + type: string + command: + description: 'Command to execute' + required: true + type: string + repository: + description: 'Repository (owner/repo)' + required: false + type: string + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + delegate: + runs-on: ubuntu-latest + if: contains(github.event.comment.body, '@claude') || github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.3.0 + with: + repository: ${{ inputs.repository || github.repository }} + + - name: Parse command + id: parse + env: + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + if [ "${{ github.event_name }}" = "issue_comment" ]; then + COMMAND=$(echo "$COMMENT_BODY" | grep -oP '@claude\s+\K.*' || echo "") + ISSUE_NUM="${{ github.event.issue.number }}" + elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + COMMAND="${{ inputs.command }}" + ISSUE_NUM="${{ inputs.issue_number }}" + else + COMMAND="" + ISSUE_NUM="" + fi + + echo "command=$COMMAND" >> $GITHUB_OUTPUT + echo "issue_number=$ISSUE_NUM" >> $GITHUB_OUTPUT + + - name: Execute command + if: steps.parse.outputs.command != '' + run: | + echo "Delegating command: ${{ steps.parse.outputs.command }}" + echo "Issue/PR: ${{ steps.parse.outputs.issue_number }}" + + # In a real implementation, this would call Claude API + # or control-center with Claude integration + echo "Command execution delegated to Claude agent" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + + - name: Acknowledge command + if: steps.parse.outputs.command != '' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ steps.parse.outputs.issue_number }}, + body: `🤖 Command received and delegated to Claude agent.\n\n---\nProcessed by Control Center Delegator` + }); diff --git a/.github/workflows/ecosystem-agents.yml b/.github/workflows/ecosystem-agents.yml deleted file mode 100644 index 892473f..0000000 --- a/.github/workflows/ecosystem-agents.yml +++ /dev/null @@ -1,352 +0,0 @@ -# Ecosystem Agents - Scheduled AI Agent Runners -# Dispatches bespoke agents nightly using Claude Haiku 4.5 (cost-efficient) -# -# Agents: -# - Bolt ⚡ (Performance) -# - Sentinel 🛡️ (Security) -# - Palette 🎨 (UX/A11y) -# - Scribe 📚 (Documentation) -# - Triage 🔍 (Issue/PR management) -# -# Model: claude-3-5-haiku-latest (fast, cost-efficient, good at coding) - -name: Ecosystem Agents - -on: - schedule: - # Nightly at 2 AM UTC - rotate through agents - - cron: '0 2 * * 0' # Sunday: Bolt (performance) - - cron: '0 2 * * 1' # Monday: Sentinel (security) - - cron: '0 2 * * 2' # Tuesday: Palette (UX) - - cron: '0 2 * * 3' # Wednesday: Scribe (docs) - - cron: '0 2 * * 4' # Thursday: Bolt - - cron: '0 2 * * 5' # Friday: Triage - - cron: '0 2 * * 6' # Saturday: Sentinel - - # Monthly deep assessment - 1st of month at 3 AM UTC - - cron: '0 3 1 * *' - - workflow_dispatch: - inputs: - agent: - description: 'Agent to run' - required: true - type: choice - options: - - bolt - - sentinel - - palette - - scribe - - triage - - assessment - target_repo: - description: 'Specific repo (owner/repo) or leave empty for self' - required: false - type: string - -permissions: - contents: write - issues: write - pull-requests: write - -env: - CLAUDE_MODEL: claude-3-5-haiku-latest - -jobs: - # ════════════════════════════════════════════════════════════════ - # DISPATCH: Determine which agent to run - # ════════════════════════════════════════════════════════════════ - dispatch: - name: Dispatch Agent - runs-on: ubuntu-latest - outputs: - agent: ${{ steps.select.outputs.agent }} - repo: ${{ steps.select.outputs.repo }} - prompt: ${{ steps.select.outputs.prompt }} - steps: - - uses: actions/checkout@v4 - - - name: Select Agent - id: select - run: | - # Determine agent from schedule or input - if [ -n "${{ inputs.agent }}" ]; then - AGENT="${{ inputs.agent }}" - else - # Select based on day of week - DOW=$(date +%w) - case $DOW in - 0) AGENT="bolt" ;; - 1) AGENT="sentinel" ;; - 2) AGENT="palette" ;; - 3) AGENT="scribe" ;; - 4) AGENT="bolt" ;; - 5) AGENT="triage" ;; - 6) AGENT="sentinel" ;; - *) AGENT="triage" ;; - esac - - # Check if it's monthly assessment day - if [ "$(date +%d)" = "01" ] && [ "$(date +%H)" = "03" ]; then - AGENT="assessment" - fi - fi - - # Target repo - if [ -n "${{ inputs.target_repo }}" ]; then - REPO="${{ inputs.target_repo }}" - else - REPO="${{ github.repository }}" - fi - - echo "agent=$AGENT" >> $GITHUB_OUTPUT - echo "repo=$REPO" >> $GITHUB_OUTPUT - - echo "🤖 Agent: $AGENT" - echo "📦 Repo: $REPO" - - - name: Load and Inject Context - id: context - run: | - AGENT="${{ steps.select.outputs.agent }}" - REPO="${{ steps.select.outputs.repo }}" - - # Load enterprise registry (we ARE the enterprise control plane) - # For managed orgs, this comes from their org-context.json - ORG_REG=$(cat .github/org-registry.json 2>/dev/null || echo "{}") - - # Repo context lives in each repo - use defaults if not present - # Repos should create their own .github/repo-context.json - if [ -f ".github/repo-context.json" ]; then - REPO_CTX=$(cat .github/repo-context.json) - else - echo "⚠️ No .github/repo-context.json found - using defaults" - REPO_CTX='{}' - fi - - # Extract values - we're the enterprise, use enterprise config - ORG_NAME=$(echo "$ORG_REG" | jq -r '.thisOrg.org // "jbcom"') - LANGUAGES="yaml,bash,javascript" # Control center languages - TEST_CMD=$(echo "$REPO_CTX" | jq -r '.commands.test // "echo No tests"') - LINT_CMD=$(echo "$REPO_CTX" | jq -r '.commands.lint // "actionlint"') - FOCUS="Enterprise control, Documentation" - - # Load metaprompt - PROMPT_FILE=".github/metaprompts/${AGENT}.md" - if [ -f "$PROMPT_FILE" ]; then - PROMPT=$(cat "$PROMPT_FILE") - else - PROMPT="You are a helpful coding assistant for $REPO." - fi - - # Inject context - PROMPT=$(echo "$PROMPT" | sed \ - -e "s|{{ORG_NAME}}|$ORG_NAME|g" \ - -e "s|{{REPO_NAME}}|${REPO##*/}|g" \ - -e "s|{{LANGUAGES}}|$LANGUAGES|g" \ - -e "s|{{TEST_COMMAND}}|$TEST_CMD|g" \ - -e "s|{{LINT_COMMAND}}|$LINT_CMD|g" \ - -e "s|{{FOCUS_AREAS}}|$FOCUS|g") - - # Save prompt to file for next job - echo "$PROMPT" > /tmp/agent-prompt.md - - echo "✅ Loaded $AGENT metaprompt with context" - - - name: Upload Prompt - uses: actions/upload-artifact@v4 - with: - name: agent-prompt - path: /tmp/agent-prompt.md - retention-days: 1 - - # ════════════════════════════════════════════════════════════════ - # CLAUDE HAIKU: Run agent via Claude Code Action (cost-efficient) - # ════════════════════════════════════════════════════════════════ - run-claude: - name: Run via Claude Haiku - needs: dispatch - if: needs.dispatch.outputs.agent != 'triage' && needs.dispatch.outputs.agent != 'assessment' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Download Prompt - uses: actions/download-artifact@v4 - with: - name: agent-prompt - path: /tmp - - - name: Run Claude Haiku Agent - uses: anthropics/claude-code-action@154d0de144ff82240e1c3deedff56280381fd122 # v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - model: ${{ env.CLAUDE_MODEL }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_tools: "Edit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(npm:*),Bash(pnpm:*),Bash(python:*),Bash(uv:*)" - direct_prompt: | - $(cat /tmp/agent-prompt.md) - - IMPORTANT INSTRUCTIONS: - 1. Analyze the codebase for improvements in your area of focus - 2. Make targeted, minimal changes (under 300 lines total) - 3. Run tests if available to verify changes - 4. Create a branch and commit your changes - 5. Push the branch and create a PR - - Branch naming: agent/${{ needs.dispatch.outputs.agent }}-$(date +%Y%m%d) - PR title format: "improve(${{ needs.dispatch.outputs.agent }}): " - - # ════════════════════════════════════════════════════════════════ - # TRIAGE: Run triage agent (uses gh CLI, not Jules) - # ════════════════════════════════════════════════════════════════ - run-triage: - name: Run Triage - needs: dispatch - if: needs.dispatch.outputs.agent == 'triage' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Triage Issues and PRs - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - REPO="${{ needs.dispatch.outputs.repo }}" - - echo "🔍 Running triage on $REPO..." - - # Count issues - TOTAL_ISSUES=$(gh issue list --repo $REPO --state open --json number --jq 'length') - UNLABELED=$(gh issue list --repo $REPO --state open --json labels --jq '[.[] | select(.labels | length == 0)] | length') - - # Count PRs - TOTAL_PRS=$(gh pr list --repo $REPO --state open --json number --jq 'length') - NEEDS_REVIEW=$(gh pr list --repo $REPO --state open --json reviewDecision --jq '[.[] | select(.reviewDecision != "APPROVED")] | length') - - # Find stale items (>30 days) - THIRTY_DAYS_AGO=$(date -d '30 days ago' +%Y-%m-%d 2>/dev/null || date -v-30d +%Y-%m-%d) - STALE_ISSUES=$(gh issue list --repo $REPO --state open --json updatedAt --jq "[.[] | select(.updatedAt < \"$THIRTY_DAYS_AGO\")] | length" 2>/dev/null || echo "0") - - # Generate report - REPORT="## 🔍 Triage Report - $(date +%Y-%m-%d) - - ### Issues - | Metric | Count | - |--------|-------| - | Total Open | $TOTAL_ISSUES | - | Unlabeled | $UNLABELED | - | Stale (>30d) | $STALE_ISSUES | - - ### Pull Requests - | Metric | Count | - |--------|-------| - | Total Open | $TOTAL_PRS | - | Needs Review | $NEEDS_REVIEW | - - --- - Generated by Ecosystem Agents - Triage" - - echo "$REPORT" - - # Create issue if there are problems - if [ "$UNLABELED" -gt 5 ] || [ "$STALE_ISSUES" -gt 3 ]; then - # Check for existing triage issue this week - EXISTING=$(gh issue list --repo $REPO --label "triage" --state open --json number --jq '.[0].number // empty') - - if [ -z "$EXISTING" ]; then - gh label create "triage" --repo $REPO --color "FBCA04" 2>/dev/null || true - gh issue create --repo $REPO \ - --title "🔍 Triage needed: $UNLABELED unlabeled, $STALE_ISSUES stale" \ - --body "$REPORT" \ - --label "triage" - echo "✅ Created triage issue" - else - gh issue comment $EXISTING --repo $REPO --body "$REPORT" - echo "✅ Updated existing triage issue #$EXISTING" - fi - fi - - # ════════════════════════════════════════════════════════════════ - # ASSESSMENT: Monthly deep codebase review - # ════════════════════════════════════════════════════════════════ - run-assessment: - name: Monthly Assessment - needs: dispatch - if: needs.dispatch.outputs.agent == 'assessment' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Deep Codebase Assessment - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - run: | - REPO="${{ needs.dispatch.outputs.repo }}" - - echo "📊 Running monthly deep assessment on $REPO..." - - # Gather metrics - TOTAL_FILES=$(find . -type f \( -name "*.ts" -o -name "*.py" -o -name "*.go" \) | wc -l) - TOTAL_LINES=$(find . -type f \( -name "*.ts" -o -name "*.py" -o -name "*.go" \) -exec wc -l {} + 2>/dev/null | tail -1 | awk '{print $1}' || echo "0") - - # Check for common issues - TODO_COUNT=$(grep -r "TODO\|FIXME\|HACK" --include="*.ts" --include="*.py" --include="*.go" . 2>/dev/null | wc -l || echo "0") - - # Check test coverage (if available) - COVERAGE="N/A" - if [ -f "coverage/lcov.info" ]; then - COVERAGE=$(grep -E "^LF:|^LH:" coverage/lcov.info | awk -F: '{sum[$1]+=$2} END {if(sum["LF"]>0) printf "%.1f%%", (sum["LH"]/sum["LF"])*100; else print "N/A"}') - fi - - # Check for outdated dependencies - OUTDATED="N/A" - if [ -f "package.json" ]; then - OUTDATED=$(npm outdated --json 2>/dev/null | jq 'length' || echo "N/A") - fi - - # Generate assessment - ASSESSMENT="## 📊 Monthly Codebase Assessment - $(date +%Y-%m) - - ### Metrics - | Metric | Value | - |--------|-------| - | Total Files | $TOTAL_FILES | - | Total Lines | $TOTAL_LINES | - | TODOs/FIXMEs | $TODO_COUNT | - | Test Coverage | $COVERAGE | - | Outdated Deps | $OUTDATED | - - ### Health Indicators - " - - # Add health checks - if [ "$TODO_COUNT" -gt 20 ]; then - ASSESSMENT="$ASSESSMENT - - ⚠️ High TODO count ($TODO_COUNT) - consider creating issues" - else - ASSESSMENT="$ASSESSMENT - - ✅ TODO count acceptable ($TODO_COUNT)" - fi - - ASSESSMENT="$ASSESSMENT - - --- - Generated by Ecosystem Agents - Monthly Assessment" - - echo "$ASSESSMENT" - - # Create assessment issue - gh label create "assessment" --repo $REPO --color "7057FF" 2>/dev/null || true - gh issue create --repo $REPO \ - --title "📊 Monthly Assessment - $(date +%Y-%m)" \ - --body "$ASSESSMENT" \ - --label "assessment" - - echo "✅ Assessment complete" diff --git a/.github/workflows/ecosystem-assessment.yml b/.github/workflows/ecosystem-assessment.yml deleted file mode 100644 index 92ab32e..0000000 --- a/.github/workflows/ecosystem-assessment.yml +++ /dev/null @@ -1,221 +0,0 @@ -# Ecosystem Assessment - Weekly Deep Codebase Analysis -# -# SPHERE 1: Deep sustained analysis using OpenAI Codex -# -# Pattern: -# 1. Codex runs for hours analyzing entire codebase -# 2. Generates reports in /reports directory -# 3. Creates starting branch with reports force-committed -# 4. Matrix outputs to Jules jobs for improvements -# -# Reports are gitignored but force-committed to the starting branch -# so Jules jobs can access them via include_last_commit - -name: Ecosystem Assessment - -on: - schedule: - # Weekly: Sunday at 2 AM UTC - - cron: '0 2 * * 0' - - workflow_dispatch: - inputs: - repository: - description: 'Repository to analyze (owner/repo)' - required: false - type: string - skip_improvements: - description: 'Skip Jules improvement jobs' - required: false - type: boolean - default: false - -permissions: - contents: write - pull-requests: write - issues: write - -env: - TARGET_REPO: ${{ inputs.repository || github.repository }} - -jobs: - # ════════════════════════════════════════════════════════════════ - # PHASE 1: Deep Analysis with Codex - # ════════════════════════════════════════════════════════════════ - analyze: - name: Deep Codebase Analysis - runs-on: ubuntu-latest - outputs: - branch: ${{ steps.branch.outputs.name }} - improvements: ${{ steps.parse.outputs.matrix }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Setup environment - run: | - # Install tools Codex might need - npm install -g pnpm - pip install uv - - # Create reports directory - mkdir -p reports - - # Ensure reports is gitignored but we can force-add - echo "reports/" >> .gitignore || true - - - name: Create analysis branch - id: branch - run: | - BRANCH="assessment/weekly-$(date +%Y-%m-%d)" - git checkout -b "$BRANCH" - echo "name=$BRANCH" >> $GITHUB_OUTPUT - - - name: Run Codex Deep Analysis - id: codex - uses: openai/codex-action@v1 - with: - openai-api-key: ${{ secrets.OPENAI_API_KEY }} - model: codex-mini-latest - sandbox: danger-full-access - effort: high - prompt-file: .github/prompts/assessment.md - output-file: reports/analysis.md - - - name: Parse improvements matrix - id: parse - run: | - # Extract improvement areas from Codex output - # Expected format in reports/analysis.md: - # ## Improvements - # - area: test-coverage - # priority: high - # description: ... - - if [ -f reports/improvements.json ]; then - MATRIX=$(jq -c '.' < reports/improvements.json) - else - # Default matrix if Codex didn't generate one - MATRIX='[{"area":"test-coverage","priority":"high"},{"area":"documentation","priority":"medium"},{"area":"security","priority":"high"},{"area":"code-quality","priority":"medium"}]' - fi - - echo "matrix=$MATRIX" >> $GITHUB_OUTPUT - - - name: Force commit reports - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Force add reports even though gitignored - git add -f reports/ - git commit -m "chore: add weekly assessment reports - - Generated by Ecosystem Assessment workflow. - These reports are used by Jules improvement jobs." - - git push -u origin "${{ steps.branch.outputs.name }}" - - - name: Upload reports artifact - uses: actions/upload-artifact@v4 - with: - name: assessment-reports - path: reports/ - retention-days: 7 - - # ════════════════════════════════════════════════════════════════ - # PHASE 2: Jules Improvement Jobs (Matrix) - # ════════════════════════════════════════════════════════════════ - improve: - name: Improve ${{ matrix.area }} - needs: analyze - if: ${{ !inputs.skip_improvements }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - max-parallel: 2 - matrix: - include: ${{ fromJson(needs.analyze.outputs.improvements) }} - steps: - - name: Invoke Jules for ${{ matrix.area }} - uses: google-labs-code/jules-action@v1 - with: - jules_api_key: ${{ secrets.GOOGLE_JULES_API_KEY }} - starting_branch: ${{ needs.analyze.outputs.branch }} - include_last_commit: true - prompt: | - You are an improvement agent focused on: ${{ matrix.area }} - Priority: ${{ matrix.priority }} - - CONTEXT: - - The assessment reports are in the /reports directory - - Read reports/analysis.md for the full codebase analysis - - Read reports/${{ matrix.area }}.md if it exists for specific findings - - YOUR TASK: - 1. Review the assessment reports for ${{ matrix.area }} issues - 2. Implement fixes for the highest priority items - 3. Run tests to verify your changes - 4. Keep changes focused and under 500 lines total - - CLEANUP: - Before creating your PR, remove the /reports directory from your branch. - The reports are only needed for context, not for the final PR. - - PR GUIDELINES: - - Title: "improve(${{ matrix.area }}): " - - Reference the assessment findings in your PR body - - Include before/after metrics if applicable - - # ════════════════════════════════════════════════════════════════ - # PHASE 3: Summary Issue - # ════════════════════════════════════════════════════════════════ - summarize: - name: Create Summary Issue - needs: [analyze, improve] - if: always() - runs-on: ubuntu-latest - steps: - - name: Download reports - uses: actions/download-artifact@v4 - with: - name: assessment-reports - path: reports/ - - - name: Create or update summary issue - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - TITLE="Weekly Assessment: $(date +%Y-%m-%d)" - - # Build issue body - BODY=$(cat <<'ISSUE_EOF' - # 📊 Weekly Codebase Assessment - - **Date:** $(date +%Y-%m-%d) - **Branch:** ${{ needs.analyze.outputs.branch }} - - ## Analysis Summary - - $(cat reports/analysis.md 2>/dev/null | head -100 || echo "See attached reports") - - ## Improvement Jobs - - | Area | Status | - |------|--------| - ${{ needs.improve.result == 'success' && '| All | ✅ Complete |' || '| See workflow | ⚠️ Check logs |' }} - - ## Next Steps - - Review the PRs created by Jules and merge as appropriate. - - --- - Generated by Ecosystem Assessment workflow - ISSUE_EOF - ) - - gh issue create --repo "${{ github.repository }}" \ - --title "$TITLE" \ - --body "$BODY" \ - --label "assessment,automated" diff --git a/.github/workflows/ecosystem-control.yml b/.github/workflows/ecosystem-control.yml deleted file mode 100644 index 7703e59..0000000 --- a/.github/workflows/ecosystem-control.yml +++ /dev/null @@ -1,384 +0,0 @@ -# Ecosystem Control - AI Agent Orchestration -# Routes @cascade commands to appropriate AI agents via pure curl -# -# Responsibilities: -# - Parse @cascade commands from issue/PR comments -# - Route to Claude, Cursor, Jules, or Ollama -# - Execute targeted AI actions against tracking issues -# -# All API calls via curl - zero dependencies - -name: Ecosystem Control - -on: - # Commands on issues or PRs - issue_comment: - types: [created] - # State transitions on tracking issues - issues: - types: [labeled] - -permissions: - contents: write - pull-requests: write - issues: write - -concurrency: - group: control-${{ github.event.issue.number }} - cancel-in-progress: false # Don't cancel AI work - -jobs: - # ════════════════════════════════════════════════════════════════ - # ROUTER: Parse command and determine agent - # ════════════════════════════════════════════════════════════════ - route: - name: Route Command - if: | - github.event_name == 'issue_comment' && - contains(github.event.comment.body, '@cascade') - runs-on: ubuntu-latest - outputs: - agent: ${{ steps.parse.outputs.agent }} - task: ${{ steps.parse.outputs.task }} - issue_num: ${{ steps.parse.outputs.issue_num }} - is_pr: ${{ steps.parse.outputs.is_pr }} - repo: ${{ steps.parse.outputs.repo }} - branch: ${{ steps.parse.outputs.branch }} - steps: - - name: Parse Command - id: parse - env: - COMMENT: ${{ github.event.comment.body }} - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - ISSUE_NUM="${{ github.event.issue.number }}" - IS_PR="${{ github.event.issue.pull_request != null }}" - - echo "issue_num=$ISSUE_NUM" >> $GITHUB_OUTPUT - echo "is_pr=$IS_PR" >> $GITHUB_OUTPUT - echo "repo=${{ github.repository }}" >> $GITHUB_OUTPUT - - # Extract task (everything after @cascade) - TASK=$(echo "$COMMENT" | sed 's/.*@cascade//' | xargs) - echo "task=$TASK" >> $GITHUB_OUTPUT - - # Get branch if PR - if [ "$IS_PR" = "true" ]; then - BRANCH=$(gh pr view $ISSUE_NUM --repo "${{ github.repository }}" --json headRefName --jq '.headRefName' 2>/dev/null || echo "main") - else - BRANCH="main" - fi - echo "branch=$BRANCH" >> $GITHUB_OUTPUT - - # Route based on keywords - AGENT="claude" # default - - if echo "$TASK" | grep -qiE "refactor|multi-file|large|migrate"; then - AGENT="jules" - elif echo "$TASK" | grep -qiE "debug|investigate|complex|fix ci"; then - AGENT="cursor" - elif echo "$TASK" | grep -qiE "quick|explain|what|why|how|question|\?"; then - AGENT="ollama" - fi - - echo "agent=$AGENT" >> $GITHUB_OUTPUT - echo "🎯 Routing to: $AGENT" - - - name: Acknowledge - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - ISSUE_NUM: ${{ github.event.issue.number }} - IS_PR: ${{ github.event.issue.pull_request != null }} - AGENT: ${{ steps.parse.outputs.agent }} - TASK: ${{ steps.parse.outputs.task }} - REPO: ${{ github.repository }} - run: | - # All values passed via env to prevent code injection - BODY=$(cat <&1) || RESPONSE="" - - if [ -z "$RESPONSE" ]; then - echo "❌ Failed to launch Cursor agent" - BODY="❌ **Cursor Agent Failed** - - Could not launch agent. Check CURSOR_API_KEY." - else - AGENT_ID=$(echo "$RESPONSE" | jq -r '.id // empty') - AGENT_URL=$(echo "$RESPONSE" | jq -r '.url // empty') - - echo "✅ Cursor agent launched: $AGENT_ID" - - BODY="🤖 **Cursor Agent Launched** - - | Field | Value | - |-------|-------| - | Agent ID | \`$AGENT_ID\` | - | Repository | \`$REPO\` | - | Branch | \`$BRANCH\` | - - [View Agent]($AGENT_URL) - - Task: - \`\`\` - $TASK - \`\`\`" - fi - - # Post result - if [ "$IS_PR" = "true" ]; then - gh pr comment $ISSUE_NUM --repo "${{ github.repository }}" --body "$BODY" - else - gh issue comment $ISSUE_NUM --repo "${{ github.repository }}" --body "$BODY" - fi - - # ════════════════════════════════════════════════════════════════ - # JULES: Pure curl to Google Jules API - # https://developers.google.com/jules/api/reference/rest - # ════════════════════════════════════════════════════════════════ - jules: - name: Jules - needs: route - if: needs.route.outputs.agent == 'jules' - runs-on: ubuntu-latest - steps: - - name: Create Jules Session - env: - GOOGLE_JULES_API_KEY: ${{ secrets.GOOGLE_JULES_API_KEY }} - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - TASK: ${{ needs.route.outputs.task }} - REPO: ${{ needs.route.outputs.repo }} - BRANCH: ${{ needs.route.outputs.branch }} - ISSUE_NUM: ${{ needs.route.outputs.issue_num }} - IS_PR: ${{ needs.route.outputs.is_pr }} - run: | - # All values passed via env to prevent code injection - # Parse owner/repo - OWNER="${REPO%%/*}" - REPO_NAME="${REPO##*/}" - - echo "🤖 Creating Jules session..." - echo " Source: sources/github/$OWNER/$REPO_NAME" - echo " Branch: $BRANCH" - echo " Task: $TASK" - - # Create session via Jules API - # https://developers.google.com/jules/api/reference/rest/v1alpha/sessions/create - RESPONSE=$(curl -sf -X POST "https://jules.googleapis.com/v1alpha/sessions" \ - -H "X-Goog-Api-Key: $GOOGLE_JULES_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg prompt "$TASK" \ - --arg source "sources/github/$OWNER/$REPO_NAME" \ - --arg branch "$BRANCH" \ - '{ - prompt: $prompt, - sourceContext: { - source: $source, - githubRepoContext: { - startingBranch: $branch - } - }, - automationMode: "AUTO_CREATE_PR" - }')" 2>&1) || RESPONSE="" - - if [ -z "$RESPONSE" ]; then - echo "❌ Failed to create Jules session" - BODY="❌ **Jules Session Failed** - - Could not create session. Check GOOGLE_JULES_API_KEY." - else - SESSION_ID=$(echo "$RESPONSE" | jq -r '.id // empty') - SESSION_URL=$(echo "$RESPONSE" | jq -r '.url // empty') - SESSION_STATE=$(echo "$RESPONSE" | jq -r '.state // "UNKNOWN"') - - echo "✅ Jules session created: $SESSION_ID" - - BODY="🤖 **Jules Session Created** - - | Field | Value | - |-------|-------| - | Session ID | \`$SESSION_ID\` | - | State | \`$SESSION_STATE\` | - | Source | \`sources/github/$OWNER/$REPO_NAME\` | - | Branch | \`$BRANCH\` | - | Mode | \`AUTO_CREATE_PR\` | - - [View Session]($SESSION_URL) - - Task: - \`\`\` - $TASK - \`\`\` - - Jules will create a PR when the work is complete." - fi - - # Post result - if [ "$IS_PR" = "true" ]; then - gh pr comment $ISSUE_NUM --repo "${{ github.repository }}" --body "$BODY" - else - gh issue comment $ISSUE_NUM --repo "${{ github.repository }}" --body "$BODY" - fi - - # ════════════════════════════════════════════════════════════════ - # OLLAMA: Pure curl to Ollama Cloud API - # ════════════════════════════════════════════════════════════════ - ollama: - name: Ollama - needs: route - if: needs.route.outputs.agent == 'ollama' - runs-on: ubuntu-latest - steps: - - name: Query Ollama - env: - OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} - OLLAMA_API_URL: ${{ vars.OLLAMA_API_URL || 'https://api.ollama.com' }} - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - TASK: ${{ needs.route.outputs.task }} - ISSUE_NUM: ${{ needs.route.outputs.issue_num }} - IS_PR: ${{ needs.route.outputs.is_pr }} - REPO: ${{ github.repository }} - run: | - # All values passed via env to prevent code injection - echo "🤖 Querying Ollama..." - - # Call Ollama API - RESPONSE=$(curl -sf --max-time 60 \ - -H "Authorization: Bearer $OLLAMA_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg prompt "$TASK" '{ - model: "llama3.2", - messages: [ - {role: "system", content: "You are a helpful coding assistant. Be concise."}, - {role: "user", content: $prompt} - ], - stream: false - }')" \ - "$OLLAMA_API_URL/api/chat" 2>&1) || RESPONSE="" - - if [ -z "$RESPONSE" ]; then - ANSWER="_(Ollama API unavailable)_" - else - ANSWER=$(echo "$RESPONSE" | jq -r '.message.content // "No response"') - fi - - BODY="🤖 **Ollama Response** - - $ANSWER - - --- - Model: llama3.2 | Cost: ~$0.001" - - # Post result - if [ "$IS_PR" = "true" ]; then - gh pr comment $ISSUE_NUM --repo "${{ github.repository }}" --body "$BODY" - else - gh issue comment $ISSUE_NUM --repo "${{ github.repository }}" --body "$BODY" - fi - - # ════════════════════════════════════════════════════════════════ - # STATE TRANSITION: Handle label changes on tracking issues - # ════════════════════════════════════════════════════════════════ - state-transition: - name: State Transition - if: | - github.event_name == 'issues' && - github.event.action == 'labeled' && - contains(github.event.issue.labels.*.name, 'synthesis') - runs-on: ubuntu-latest - steps: - - name: Handle State Change - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - ISSUE_NUM="${{ github.event.issue.number }}" - LABEL="${{ github.event.label.name }}" - - echo "🏷️ Label added: $LABEL on issue #$ISSUE_NUM" - - case "$LABEL" in - queue-ready) - echo " → Triggering merge assessment" - # The merge workflow will pick this up - ;; - queue-blocked) - echo " → PR is blocked by critical issues" - ;; - queue-review) - echo " → PR needs review attention" - ;; - esac diff --git a/.github/workflows/ecosystem-delegator.yml b/.github/workflows/ecosystem-delegator.yml deleted file mode 100644 index 5b63e8a..0000000 --- a/.github/workflows/ecosystem-delegator.yml +++ /dev/null @@ -1,225 +0,0 @@ -name: Ecosystem Delegator - -# Handles delegation commands in issue comments: -# - /jules - delegate to Google Jules -# - /cursor - delegate to Cursor agent -# - @claude - delegate to Claude - -on: - issue_comment: - types: [created] - - # Enterprise Reusable Workflow - workflow_call: - inputs: - comment_body: - description: 'The comment that triggered delegation' - required: true - type: string - issue_number: - description: 'Issue number' - required: true - type: number - issue_title: - description: 'Issue title' - required: false - type: string - issue_body: - description: 'Issue body/description' - required: false - type: string - repository: - description: 'Repository owner/name' - required: true - type: string - default_branch: - description: 'Default branch of the target repository' - required: false - default: 'main' - type: string - secrets: - CI_GITHUB_TOKEN: - required: true - GOOGLE_JULES_API_KEY: - required: false - CURSOR_API_KEY: - required: false - ANTHROPIC_API_KEY: - required: false - - # Manual/On-call trigger - workflow_dispatch: - inputs: - comment_body: - description: 'Command (e.g. /jules fix this)' - required: true - type: string - issue_number: - description: 'Issue number' - required: true - type: number - issue_title: - description: 'Issue title' - required: false - type: string - issue_body: - description: 'Issue body/description' - required: false - type: string - repository: - description: 'Repository' - required: true - type: string - default_branch: - description: 'Default branch of the target repository' - default: 'main' - type: string - -env: - TARGET_REPO: ${{ inputs.repository || github.repository }} - TARGET_ISSUE: ${{ inputs.issue_number || github.event.issue.number }} - ISSUE_TITLE: ${{ inputs.issue_title || github.event.issue.title }} - ISSUE_BODY: ${{ inputs.issue_body || github.event.issue.body }} - COMMENT_BODY: ${{ inputs.comment_body || github.event.comment.body }} - -permissions: - contents: write - issues: write - pull-requests: write - actions: write - -jobs: - # ============================================ - # Jules/Cursor delegation - # ============================================ - delegate-command: - name: Delegate to Jules/Cursor - if: | - (github.event_name == 'issue_comment' && github.event.issue.pull_request == null && (contains(github.event.comment.body, '/jules') || contains(github.event.comment.body, '/cursor'))) || - (github.event_name == 'workflow_call' && (contains(inputs.comment_body, '/jules') || contains(inputs.comment_body, '/cursor'))) || - (github.event_name == 'workflow_dispatch' && (contains(inputs.comment_body, '/jules') || contains(inputs.comment_body, '/cursor'))) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - repository: ${{ env.TARGET_REPO }} - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Create Jules Session from Issue - if: contains(env.COMMENT_BODY, '/jules') - env: - GOOGLE_JULES_API_KEY: ${{ secrets.GOOGLE_JULES_API_KEY }} - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - DEFAULT_BRANCH: ${{ inputs.default_branch || github.event.repository.default_branch || 'main' }} - run: | - # Extract prompt from comment, removing the /jules command - PROMPT=$(echo "${COMMENT_BODY}" | sed -n 's|/jules ||p') - - if [[ -z "$PROMPT" ]]; then - echo "Jules command used without a prompt. Exiting." - exit 0 - fi - - RESPONSE=$(curl -s -X POST "https://jules.googleapis.com/v1alpha/sessions" \ - -H "X-Goog-Api-Key: $GOOGLE_JULES_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg task "$PROMPT" \ - --arg repo "$TARGET_REPO" \ - --arg branch "$DEFAULT_BRANCH" \ - '{ - prompt: $task, - sourceContext: { - source: ("sources/github/" + ($repo | split("/")[0]) + "/" + ($repo | split("/")[1])), - githubRepoContext: { startingBranch: $branch } - }, - automationMode: "AUTO_CREATE_PR", - title: ("Jules: Address issue #" + env.TARGET_ISSUE) - }')") - - SESSION_ID=$(echo "$RESPONSE" | jq -r '.name // empty' | sed 's|sessions/||') - - if [[ -n "$SESSION_ID" ]]; then - gh issue comment "$TARGET_ISSUE" --repo "$TARGET_REPO" --body "✅ **Jules session created**: https://jules.google.com/session/${SESSION_ID}" - else - gh issue comment "$TARGET_ISSUE" --repo "$TARGET_REPO" --body "❌ **Error creating Jules session.**" - echo "Failed to create Jules session. Response: $RESPONSE" - exit 1 - fi - - - name: Create Cursor Cloud Agent - if: contains(env.COMMENT_BODY, '/cursor') - env: - CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - # Extract prompt from comment, removing the /cursor command - PROMPT=$(echo "${COMMENT_BODY}" | sed -n 's|/cursor ||p') - - if [[ -z "$PROMPT" ]]; then - echo "Cursor command used without a prompt. Exiting." - exit 0 - fi - - RESPONSE=$(curl -s -X POST "https://api.cursor.com/v0/agents" \ - -u "$CURSOR_API_KEY:" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg task "$PROMPT" \ - --arg repo "$TARGET_REPO" \ - '{"prompt":{"text":$task},"source":{"repository":$repo}}')") - - AGENT_ID=$(echo "$RESPONSE" | jq -r '.id // empty') - - if [[ -n "$AGENT_ID" ]]; then - gh issue comment "$TARGET_ISSUE" --repo "$TARGET_REPO" --body "✅ **Cursor agent spawned**: ID $AGENT_ID" - else - gh issue comment "$TARGET_ISSUE" --repo "$TARGET_REPO" --body "❌ **Error spawning Cursor agent.**" - echo "Failed to spawn Cursor agent. Response: $RESPONSE" - exit 1 - fi - - # ============================================ - # Claude delegation (@claude in issues) - # ============================================ - delegate-claude: - name: Delegate to Claude - if: | - (github.event_name == 'issue_comment' && github.event.issue.pull_request == null && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'workflow_call' && contains(inputs.comment_body, '@claude')) || - (github.event_name == 'workflow_dispatch' && contains(inputs.comment_body, '@claude')) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - repository: ${{ env.TARGET_REPO }} - fetch-depth: 0 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Run Claude on Issue - uses: anthropics/claude-code-action@154d0de144ff82240e1c3deedff56280381fd122 # main - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - allowed_tools: "Edit,Write,Read,Glob,Grep,LS,Bash(git status),Bash(git diff),Bash(git add),Bash(git commit),Bash(git push),Bash(gh pr view),Bash(gh pr create),Bash(gh issue view),Bash(node:*),Bash(npm:*)" - direct_prompt: | - Issue in ${{ env.TARGET_REPO }}: #${{ env.TARGET_ISSUE }} - - Title: ${{ env.ISSUE_TITLE }} - - Description: - ${{ env.ISSUE_BODY }} - - --- - - Triggering comment: ${{ env.COMMENT_BODY }} - - --- - - Analyze the issue and either: - 1. Answer the question if it's a question - 2. Implement a fix if it's a bug - 3. Implement the feature if it's a feature request - - Create a PR with your changes. diff --git a/.github/workflows/ecosystem-fixer.yml b/.github/workflows/ecosystem-fixer.yml deleted file mode 100644 index f759a5f..0000000 --- a/.github/workflows/ecosystem-fixer.yml +++ /dev/null @@ -1,233 +0,0 @@ -name: Ecosystem Fixer - -# CI Failure Resolution Pipeline -# -# Uses Ollama GLM 4.6 Cloud for cost-efficient, fast auto-fix suggestions. -# Combined with CodeQL and Copilot suggestions for comprehensive fixes. -# -# Pattern: API analysis → Ollama suggestion → PR comment - -on: - workflow_run: - workflows: ["CI", "Build", "Test", "Lint"] - types: [completed] - branches-ignore: [main] - - workflow_call: - inputs: - run_id: - description: 'Workflow run ID that failed' - required: true - type: string - repository: - description: 'Repository owner/name' - required: true - type: string - branch: - description: 'Branch name' - required: true - type: string - secrets: - CI_GITHUB_TOKEN: - required: true - OLLAMA_API_KEY: - required: false - - workflow_dispatch: - inputs: - run_id: - description: 'Workflow run ID' - required: true - type: string - repository: - description: 'Repository' - required: true - type: string - branch: - description: 'Branch' - required: true - type: string - -permissions: - contents: read - pull-requests: write - actions: read - issues: write - -env: - OLLAMA_HOST: https://ollama.com - OLLAMA_MODEL: glm-4.6:cloud - -jobs: - # ════════════════════════════════════════════════════════════════ - # ANALYZE: Extract failure context via API - # ════════════════════════════════════════════════════════════════ - analyze: - name: Analyze Failure - if: | - (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure') || - github.event_name == 'workflow_call' || - github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - outputs: - is_fork: ${{ steps.context.outputs.is_fork }} - pr_number: ${{ steps.context.outputs.pr_number }} - failure_log: ${{ steps.context.outputs.failure_log }} - target_repo: ${{ steps.context.outputs.target_repo }} - target_branch: ${{ steps.context.outputs.target_branch }} - steps: - - name: Extract failure context via API - id: context - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - INPUT_REPO: ${{ inputs.repository || github.repository }} - INPUT_BRANCH: ${{ inputs.branch || github.event.workflow_run.head_branch }} - INPUT_RUN_ID: ${{ inputs.run_id || github.event.workflow_run.id }} - EVENT_NAME: ${{ github.event_name }} - HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }} - BASE_REPO: ${{ github.event.workflow_run.repository.full_name }} - run: | - # Resolve target (via env vars to prevent code injection) - TARGET_REPO="$INPUT_REPO" - TARGET_BRANCH="$INPUT_BRANCH" - TARGET_RUN_ID="$INPUT_RUN_ID" - - echo "target_repo=$TARGET_REPO" >> $GITHUB_OUTPUT - echo "target_branch=$TARGET_BRANCH" >> $GITHUB_OUTPUT - - # Security: Check if fork PR (using env vars to prevent code injection) - if [ "$EVENT_NAME" = "workflow_run" ]; then - if [ "$HEAD_REPO" != "$BASE_REPO" ]; then - echo "⚠️ Fork PR detected" - echo "is_fork=true" >> $GITHUB_OUTPUT - exit 0 - fi - fi - echo "is_fork=false" >> $GITHUB_OUTPUT - - # Get PR number - PR_NUM=$(gh pr list --repo "$TARGET_REPO" --head "$TARGET_BRANCH" --json number --jq '.[0].number // empty' 2>/dev/null || echo "") - echo "pr_number=$PR_NUM" >> $GITHUB_OUTPUT - - # Get failure logs (truncated for Ollama context window) - echo "📋 Fetching failure logs..." - LOGS=$(gh run view "$TARGET_RUN_ID" --repo "$TARGET_REPO" --log-failed 2>&1 | tail -300 || echo "Could not fetch logs") - - # Save to file for artifact - echo "$LOGS" > /tmp/failure.log - echo "failure_log=/tmp/failure.log" >> $GITHUB_OUTPUT - - - name: Upload failure log - uses: actions/upload-artifact@v4 - with: - name: failure-log - path: /tmp/failure.log - retention-days: 1 - - # ════════════════════════════════════════════════════════════════ - # FIX: Query Ollama GLM 4.6 for fix suggestions - # ════════════════════════════════════════════════════════════════ - suggest-fix: - name: Suggest Fix (Ollama) - needs: analyze - if: | - needs.analyze.outputs.is_fork != 'true' && - needs.analyze.outputs.pr_number != '' - runs-on: ubuntu-latest - steps: - - name: Download failure log - uses: actions/download-artifact@v4 - with: - name: failure-log - path: /tmp - - - name: Query Ollama for fix - env: - OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - TARGET_REPO: ${{ needs.analyze.outputs.target_repo }} - TARGET_BRANCH: ${{ needs.analyze.outputs.target_branch }} - PR_NUM: ${{ needs.analyze.outputs.pr_number }} - run: | - if [ -z "$OLLAMA_API_KEY" ]; then - echo "⚠️ No OLLAMA_API_KEY configured" - exit 0 - fi - - FAILURE_LOG=$(head -200 /tmp/failure.log) - - # Build prompt using heredoc - PROMPT=$(cat <&1) || RESPONSE="" - - if [ -z "$RESPONSE" ]; then - echo "⚠️ Ollama API call failed" - SUGGESTION="_(Auto-fix analysis unavailable. Please review the CI logs manually.)_" - else - SUGGESTION=$(echo "$RESPONSE" | jq -r '.message.content // "No suggestion generated"') - fi - - # Post suggestion to PR - COMMENT_BODY=$(cat <🤖 Generated by Ecosystem Fixer using Ollama GLM 4.6 - COMMENT_EOF - ) - - gh pr comment "$PR_NUM" --repo "$TARGET_REPO" --body "$COMMENT_BODY" - echo "✅ Posted fix suggestion to PR #$PR_NUM" - - # ════════════════════════════════════════════════════════════════ - # NOTIFY: Report status for fork PRs - # ════════════════════════════════════════════════════════════════ - notify-fork: - name: Notify Fork PR - needs: analyze - if: needs.analyze.outputs.is_fork == 'true' && needs.analyze.outputs.pr_number != '' - runs-on: ubuntu-latest - steps: - - name: Post notice - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - TARGET_REPO: ${{ needs.analyze.outputs.target_repo }} - PR_NUM: ${{ needs.analyze.outputs.pr_number }} - run: | - gh pr comment "$PR_NUM" --repo "$TARGET_REPO" --body "$(cat <<'EOF' - ⚠️ **Auto-fix unavailable for fork PRs** - - For security reasons, automatic CI fixes are disabled for fork PRs. - Please fix the CI failure manually. - EOF - )" diff --git a/.github/workflows/ecosystem-merge.yml b/.github/workflows/ecosystem-merge.yml deleted file mode 100644 index b1ec2b9..0000000 --- a/.github/workflows/ecosystem-merge.yml +++ /dev/null @@ -1,341 +0,0 @@ -# Ecosystem Merge - Merge Queue Consumer -# Reads tracking issue state and executes merge decisions -# -# Responsibilities: -# - Read verdict from tracking issue (KV store) -# - Check CI status -# - Auto-approve eligible PRs -# - Auto-rebase when needed -# - Execute merge -# -# Coordination: Triggered by CI completion or tracking issue state changes - -name: Ecosystem Merge - -on: - # CI completion events - check_suite: - types: [completed] - check_run: - types: [completed] - # Tracking issue state changes - issues: - types: [labeled] - # PR state changes - pull_request: - types: [labeled, unlabeled] - # Manual merge request - issue_comment: - types: [created] - -permissions: - contents: write - pull-requests: write - issues: write - checks: read - -concurrency: - group: merge-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }} - cancel-in-progress: true - -jobs: - # ════════════════════════════════════════════════════════════════ - # MERGE ASSESSMENT: Read KV store, check CI, decide - # ════════════════════════════════════════════════════════════════ - assess: - name: Merge Assessment - if: | - (github.event_name == 'check_suite' && - github.event.check_suite.conclusion == 'success' && - github.event.check_suite.pull_requests[0] != null) || - (github.event_name == 'check_run' && - github.event.check_run.conclusion == 'success' && - github.event.check_run.pull_requests[0] != null) || - (github.event_name == 'issues' && - github.event.action == 'labeled' && - github.event.label.name == 'queue-ready') || - (github.event_name == 'pull_request') || - (github.event_name == 'issue_comment' && - contains(github.event.comment.body, '@cascade merge')) - runs-on: ubuntu-latest - outputs: - can_merge: ${{ steps.assess.outputs.can_merge }} - pr_number: ${{ steps.assess.outputs.pr_number }} - steps: - - name: Assess Merge Eligibility - id: assess - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - ISSUE_TITLE: ${{ github.event.issue.title }} - run: | - # ══════════════════════════════════════════════════════════ - # RESOLVE PR NUMBER - # ══════════════════════════════════════════════════════════ - case "${{ github.event_name }}" in - check_suite) - PR_NUM=$(echo '${{ toJson(github.event.check_suite.pull_requests) }}' | jq -r '.[0].number // empty') - ;; - check_run) - PR_NUM=$(echo '${{ toJson(github.event.check_run.pull_requests) }}' | jq -r '.[0].number // empty') - ;; - pull_request) - PR_NUM="${{ github.event.pull_request.number }}" - ;; - issue_comment) - if [ -n "${{ github.event.issue.pull_request }}" ]; then - PR_NUM="${{ github.event.issue.number }}" - else - # Comment on tracking issue - extract PR number from title (via env var for security) - PR_NUM=$(echo "$ISSUE_TITLE" | grep -oE 'pr-([0-9]+)' | grep -oE '[0-9]+' || echo "") - fi - ;; - issues) - # Tracking issue labeled - extract PR number from title (via env var for security) - PR_NUM=$(echo "$ISSUE_TITLE" | grep -oE 'pr-([0-9]+)' | grep -oE '[0-9]+' || echo "") - ;; - *) - PR_NUM="" - ;; - esac - - [ -z "$PR_NUM" ] && echo "No PR number" && exit 0 - echo "pr_number=$PR_NUM" >> $GITHUB_OUTPUT - - echo "╔════════════════════════════════════════════════════════════╗" - echo "║ ⚖️ MERGE ASSESSMENT: PR #$PR_NUM ║" - echo "╚════════════════════════════════════════════════════════════╝" - - # ══════════════════════════════════════════════════════════ - # GET PR STATE - # ══════════════════════════════════════════════════════════ - PR_DATA=$(gh pr view $PR_NUM --repo "${{ github.repository }}" --json isDraft,mergeable,reviewDecision,title,labels,mergeStateStatus 2>/dev/null || echo "{}") - - IS_DRAFT=$(echo "$PR_DATA" | jq -r '.isDraft // false') - MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // "UNKNOWN"') - MERGE_STATE=$(echo "$PR_DATA" | jq -r '.mergeStateStatus // "UNKNOWN"') - REVIEW=$(echo "$PR_DATA" | jq -r '.reviewDecision // "REVIEW_REQUIRED"') - TITLE=$(echo "$PR_DATA" | jq -r '.title // "Unknown"') - # shellcheck disable=SC2034 - HAS_AUTO=$(echo "$PR_DATA" | jq '[.labels[]?.name // empty] | map(select(. == "auto-merge")) | length') - - echo "" - echo "📋 PR: $TITLE" - echo " Draft: $IS_DRAFT | Mergeable: $MERGEABLE | State: $MERGE_STATE" - - [ "$IS_DRAFT" = "true" ] && echo "⏸️ Draft - skipping" && exit 0 - - # ══════════════════════════════════════════════════════════ - # READ TRACKING ISSUE (KV Store) - # ══════════════════════════════════════════════════════════ - echo "" - echo "📖 Reading tracking issue..." - - TRACKING=$(gh issue list --repo "${{ github.repository }}" --label "synthesis" --search "synthesis-pr-$PR_NUM in:title" --json number,labels --jq '.[0]' 2>/dev/null || echo "{}") - TRACKING_NUM=$(echo "$TRACKING" | jq -r '.number // empty') - - VERDICT="UNKNOWN" - if [ -n "$TRACKING_NUM" ]; then - # Check labels for verdict - if echo "$TRACKING" | jq -e '.labels[] | select(.name == "queue-blocked")' > /dev/null 2>&1; then - VERDICT="BLOCKED" - elif echo "$TRACKING" | jq -e '.labels[] | select(.name == "queue-ready")' > /dev/null 2>&1; then - VERDICT="READY" - elif echo "$TRACKING" | jq -e '.labels[] | select(.name == "queue-review")' > /dev/null 2>&1; then - VERDICT="REVIEW" - fi - echo " Tracking issue: #$TRACKING_NUM → $VERDICT" - else - echo " No tracking issue found" - VERDICT="CLEAR" # No tracking = no known blockers - fi - - # ══════════════════════════════════════════════════════════ - # CHECK CI STATUS - # ══════════════════════════════════════════════════════════ - echo "" - echo "🔬 Checking CI..." - - FAILED=$(gh pr checks $PR_NUM --json conclusion --jq '[.[] | select(.conclusion == "FAILURE" or .conclusion == "TIMED_OUT")] | length' 2>/dev/null || echo "0") - PENDING=$(gh pr checks $PR_NUM --json state --jq '[.[] | select(.state == "PENDING" or .state == "QUEUED")] | length' 2>/dev/null || echo "0") - - echo " Failed: $FAILED | Pending: $PENDING" - - CI_READY=false - [ "$FAILED" -eq 0 ] && [ "$PENDING" -eq 0 ] && CI_READY=true - - # ══════════════════════════════════════════════════════════ - # DECISION - # ══════════════════════════════════════════════════════════ - echo "" - echo "┌─────────────────────────────────────────────────────────┐" - echo "│ DECISION MATRIX │" - echo "├───────────────────────┬─────────────────────────────────┤" - printf "│ %-21s │ %-31s │\n" "CI Ready" "$([ "$CI_READY" = true ] && echo '✅' || echo '❌')" - printf "│ %-21s │ %-31s │\n" "Mergeable" "$([ "$MERGEABLE" = 'MERGEABLE' ] && echo '✅' || echo "❌ $MERGEABLE")" - printf "│ %-21s │ %-31s │\n" "Verdict" "$VERDICT" - printf "│ %-21s │ %-31s │\n" "Review" "$REVIEW" - echo "└───────────────────────┴─────────────────────────────────┘" - - CAN_MERGE=false - REASON="" - - if [ "$CI_READY" = true ] && [ "$MERGEABLE" = "MERGEABLE" ]; then - case "$VERDICT" in - READY|CLEAR) - CAN_MERGE=true - REASON="CI ✅ + Mergeable ✅ + Queue clear" - ;; - REVIEW) - CAN_MERGE=true - REASON="CI ✅ + Mergeable ✅ + Non-critical feedback" - ;; - BLOCKED) - REASON="Blocked by critical issues in tracking issue" - ;; - *) - CAN_MERGE=true - REASON="CI ✅ + Mergeable ✅ + No blockers" - ;; - esac - elif [ "$CI_READY" = false ]; then - REASON="CI not ready ($FAILED failed, $PENDING pending)" - elif [ "$MERGEABLE" != "MERGEABLE" ]; then - REASON="PR not mergeable ($MERGEABLE)" - fi - - echo "" - echo "can_merge=$CAN_MERGE" >> $GITHUB_OUTPUT - - if [ "$CAN_MERGE" = true ]; then - echo "✅ ELIGIBLE: $REASON" - else - echo "⏸️ NOT ELIGIBLE: $REASON" - fi - - # ════════════════════════════════════════════════════════════════ - # APPROVE: Auto-approve eligible PRs - # ════════════════════════════════════════════════════════════════ - approve: - name: Auto-Approve - needs: assess - if: needs.assess.outputs.can_merge == 'true' - runs-on: ubuntu-latest - steps: - - name: Approve PR - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - PR_NUM: ${{ needs.assess.outputs.pr_number }} - run: | - # PR_NUM passed via env to prevent code injection - - # Check current review state - REVIEW=$(gh pr view $PR_NUM --repo "${{ github.repository }}" --json reviewDecision --jq '.reviewDecision' 2>/dev/null || echo "") - - if [ "$REVIEW" = "APPROVED" ]; then - echo "✅ Already approved" - exit 0 - fi - - echo "📝 Approving PR #$PR_NUM..." - - gh pr review $PR_NUM --approve --body "✅ **Auto-approved by Ecosystem Merge** - - | Check | Status | - |-------|--------| - | CI | ✅ Passed | - | Mergeable | ✅ Ready | - | Queue | ✅ Clear | - - Proceeding with merge." 2>/dev/null || echo "Could not approve (may need different token)" - - # Add auto-merge label - gh label create "auto-merge" --repo "${{ github.repository }}" --color "0E8A16" 2>/dev/null || true - gh pr edit $PR_NUM --repo "${{ github.repository }}" --add-label "auto-merge" 2>/dev/null || true - - # ════════════════════════════════════════════════════════════════ - # REBASE: Auto-rebase if behind - # ════════════════════════════════════════════════════════════════ - rebase: - name: Auto-Rebase - needs: assess - if: needs.assess.outputs.can_merge == 'true' - runs-on: ubuntu-latest - steps: - - name: Check and Rebase - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - PR_NUM: ${{ needs.assess.outputs.pr_number }} - run: | - # PR_NUM passed via env to prevent code injection - - # Check merge state - MERGE_STATE=$(gh pr view $PR_NUM --repo "${{ github.repository }}" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null || echo "") - - if [ "$MERGE_STATE" != "BEHIND" ]; then - echo "✅ PR is up to date" - exit 0 - fi - - echo "🔄 PR is behind, attempting rebase..." - - # Try GitHub's update branch API - gh api -X PUT repos/${{ github.repository }}/pulls/$PR_NUM/update-branch \ - -f update_method=rebase 2>/dev/null && echo "✅ Rebased via API" && exit 0 - - echo "⚠️ API rebase failed, manual rebase may be needed" - - # ════════════════════════════════════════════════════════════════ - # MERGE: Execute the merge - # ════════════════════════════════════════════════════════════════ - merge: - name: Execute Merge - needs: [assess, approve, rebase] - if: | - always() && - needs.assess.outputs.can_merge == 'true' && - (needs.approve.result == 'success' || needs.approve.result == 'skipped') && - (needs.rebase.result == 'success' || needs.rebase.result == 'skipped') - runs-on: ubuntu-latest - steps: - - name: Merge PR - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - PR_NUM: ${{ needs.assess.outputs.pr_number }} - run: | - # PR_NUM passed via env to prevent code injection - - echo "🚀 Attempting to merge PR #$PR_NUM..." - - # Final check - PR_DATA=$(gh pr view $PR_NUM --repo "${{ github.repository }}" --json mergeable,mergeStateStatus,isDraft) - MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable') - STATE=$(echo "$PR_DATA" | jq -r '.mergeStateStatus') - DRAFT=$(echo "$PR_DATA" | jq -r '.isDraft') - - if [ "$DRAFT" = "true" ]; then - echo "⏸️ PR is draft - cannot merge" - exit 0 - fi - - if [ "$MERGEABLE" != "MERGEABLE" ]; then - echo "⏸️ PR not mergeable: $MERGEABLE" - exit 0 - fi - - if [ "$STATE" = "BEHIND" ]; then - echo "⏸️ PR is behind - waiting for rebase" - exit 0 - fi - - # Merge with squash - if gh pr merge $PR_NUM --squash --delete-branch 2>/dev/null; then - echo "✅ PR #$PR_NUM merged successfully!" - - # Close tracking issue - TRACKING=$(gh issue list --repo "${{ github.repository }}" --label "synthesis" --search "synthesis-pr-$PR_NUM in:title" --json number --jq '.[0].number // empty' 2>/dev/null) - if [ -n "$TRACKING" ]; then - gh issue close $TRACKING --repo "${{ github.repository }}" --comment "✅ PR #$PR_NUM merged." 2>/dev/null || true - fi - else - echo "⚠️ Merge failed - may need manual intervention" - fi diff --git a/.github/workflows/ecosystem-reviewer.yml b/.github/workflows/ecosystem-reviewer.yml deleted file mode 100644 index 1289963..0000000 --- a/.github/workflows/ecosystem-reviewer.yml +++ /dev/null @@ -1,252 +0,0 @@ -name: Ecosystem Reviewer - -# Tiered AI Review Pipeline: -# 1. Ollama (fast, cheap) → quick review -# 2. Claude (escalation) → complex PRs or many issues -# 3. Jules (refactor) → when code changes needed - -on: - pull_request: - types: [opened, synchronize, ready_for_review] - # Also handle @claude mentions in PR comments - issue_comment: - types: [created] - - # Enterprise Reusable Workflow - workflow_call: - inputs: - pr_number: - description: 'PR number to review' - required: true - type: number - repository: - description: 'Repository to review (owner/repo)' - required: true - type: string - model_tier: - description: 'Model tier to use (ollama, claude, jules, all)' - required: false - default: 'ollama' - type: string - secrets: - CI_GITHUB_TOKEN: - required: true - ANTHROPIC_API_KEY: - required: false - GOOGLE_JULES_API_KEY: - required: false - OLLAMA_API_KEY: - required: false - - # Manual/On-call trigger - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to review' - required: true - type: number - repository: - description: 'Repository to review' - required: true - type: string - model_tier: - description: 'Model tier (ollama, claude, jules, all)' - default: 'ollama' - type: string - -env: - OLLAMA_HOST: ${{ secrets.OLLAMA_API_URL || vars.OLLAMA_API_URL || vars.OLLAMA_HOST || 'https://ollama.com' }} - OLLAMA_MODEL: ${{ vars.OLLAMA_MODEL || 'glm-4.6:cloud' }} - TARGET_REPO: ${{ inputs.repository || github.repository }} - TARGET_PR: ${{ inputs.pr_number || github.event.pull_request.number || github.event.issue.number }} - -permissions: - contents: write - pull-requests: write - issues: write - -jobs: - # ============================================ - # TIER 1: Ollama Quick Review - # ============================================ - ollama-review: - name: Ollama Review - if: | - (github.event_name == 'pull_request' && github.event.pull_request.draft == false) || - (github.event_name == 'workflow_call' && (inputs.model_tier == 'ollama' || inputs.model_tier == 'all')) || - (github.event_name == 'workflow_dispatch' && (inputs.model_tier == 'ollama' || inputs.model_tier == 'all')) - runs-on: ubuntu-latest - outputs: - suggestion_count: ${{ steps.review.outputs.suggestion_count }} - needs_escalation: ${{ steps.check.outputs.needs_escalation }} - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - repository: ${{ env.TARGET_REPO }} - fetch-depth: 0 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Review PR - id: review - uses: jbcom/control-center/.github/actions/agentic-pr-review@main - with: - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - model: ${{ env.OLLAMA_MODEL }} - ollama_api_key: ${{ secrets.OLLAMA_API_KEY }} - ollama_host: ${{ env.OLLAMA_HOST }} - jules_api_key: ${{ secrets.GOOGLE_JULES_API_KEY }} - - - name: Check if escalation needed - id: check - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - SUGGESTIONS="${{ steps.review.outputs.suggestion_count }}" - ADDITIONS=$(gh pr view ${{ env.TARGET_PR }} --repo ${{ env.TARGET_REPO }} --json additions --jq '.additions') - - # Escalate if: >5 suggestions OR >500 lines changed OR Ollama failed - if [[ "$SUGGESTIONS" -gt 5 ]] || [[ "$ADDITIONS" -gt 500 ]] || [[ -z "$SUGGESTIONS" ]]; then - echo "needs_escalation=true" >> $GITHUB_OUTPUT - else - echo "needs_escalation=false" >> $GITHUB_OUTPUT - fi - - # ============================================ - # TIER 2: Claude Escalation (complex PRs) - # ============================================ - claude-review: - name: Claude Review - needs: ollama-review - if: | - always() && ( - needs.ollama-review.outputs.needs_escalation == 'true' || - (github.event_name == 'workflow_call' && inputs.model_tier == 'claude') || - (github.event_name == 'workflow_dispatch' && inputs.model_tier == 'claude') - ) - runs-on: ubuntu-latest - outputs: - critical_issues: ${{ steps.claude.outputs.critical_issues }} - steps: - - name: Check API Key - id: check-key - run: | - if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "available=false" >> $GITHUB_OUTPUT - fi - - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - if: steps.check-key.outputs.available == 'true' - with: - repository: ${{ env.TARGET_REPO }} - fetch-depth: 0 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Claude Code Review - if: steps.check-key.outputs.available == 'true' - id: claude - uses: anthropics/claude-code-action@154d0de144ff82240e1c3deedff56280381fd122 # main - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - allowed_tools: "Read,Glob,Grep,LS,Bash(git diff),Bash(gh pr view),Bash(gh pr list),Bash(gh issue view)" - direct_prompt: | - Review this PR thoroughly in ${{ env.TARGET_REPO }}. Focus on: - - Security issues (tokens, secrets, injection) - - Logic errors and bugs - - Performance problems - - Missing error handling - - PR: #${{ env.TARGET_PR }} - - If no critical issues, APPROVE the PR. - Post inline comments for specific issues. - - # ============================================ - # TIER 3: Jules Refactor (when code changes needed) - # ============================================ - jules-refactor: - name: Jules Refactor - needs: [ollama-review, claude-review] - if: | - always() && ( - (needs.ollama-review.result == 'success' && needs.ollama-review.outputs.suggestion_count > 10) || - (github.event_name == 'workflow_call' && inputs.model_tier == 'jules') || - (github.event_name == 'workflow_dispatch' && inputs.model_tier == 'jules') - ) - runs-on: ubuntu-latest - steps: - - name: Check for API Key - id: check - run: | - if [[ -z "${{ secrets.GOOGLE_JULES_API_KEY }}" ]]; then - echo "available=false" >> $GITHUB_OUTPUT - else - echo "available=true" >> $GITHUB_OUTPUT - fi - - - name: Create Jules Session from PR - if: steps.check.outputs.available == 'true' - id: jules - env: - GOOGLE_JULES_API_KEY: ${{ secrets.GOOGLE_JULES_API_KEY }} - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - set -eo pipefail - - PR_DATA=$(gh pr view "$TARGET_PR" --repo "$TARGET_REPO" --json title,headRefName) - PR_TITLE=$(echo "$PR_DATA" | jq -r .title) - PR_BRANCH=$(echo "$PR_DATA" | jq -r .headRefName) - - RESPONSE=$(curl -sf -X POST "https://jules.googleapis.com/v1alpha/sessions" \ - -H "X-Goog-Api-Key: $GOOGLE_JULES_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg task "Refactor PR #${TARGET_PR}: ${PR_TITLE}" \ - --arg repo "$TARGET_REPO" \ - --arg branch "$PR_BRANCH" \ - '{ - prompt: $task, - sourceContext: { - source: ("sources/github/" + ($repo | split("/")[0]) + "/" + ($repo | split("/")[1])), - githubRepoContext: { startingBranch: $branch } - }, - automationMode: "AUTO_CREATE_PR" - }')") - - SESSION_ID=$(echo "$RESPONSE" | jq -r '.name // empty' | sed 's|sessions/||') - - if [[ -n "$SESSION_ID" ]]; then - gh pr comment "$TARGET_PR" --repo "$TARGET_REPO" --body "🔧 **Jules refactoring session started**: https://jules.google.com/session/${SESSION_ID}" - else - gh pr comment "$TARGET_PR" --repo "$TARGET_REPO" --body "❌ **Error creating Jules refactoring session.**" - echo "Failed to create Jules session. Response: $RESPONSE" - exit 1 - fi - - # ============================================ - # INTERACTIVE: @claude mentions - # ============================================ - claude-interactive: - name: Claude Interactive - if: | - github.event_name == 'issue_comment' && - github.event.issue.pull_request && - contains(github.event.comment.body, '@claude') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - repository: ${{ env.TARGET_REPO }} - fetch-depth: 0 - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Run Claude - uses: anthropics/claude-code-action@154d0de144ff82240e1c3deedff56280381fd122 # main - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.CI_GITHUB_TOKEN }} - allowed_bots: "google-labs-jules[bot],copilot-swe-agent[bot],dependabot[bot],renovate[bot],github-actions[bot]" - allowed_tools: "Edit,Write,Read,Glob,Grep,LS,Bash(git status),Bash(git diff),Bash(git add),Bash(git commit),Bash(git push),Bash(gh pr view),Bash(gh pr list),Bash(node:*),Bash(npm:*)" diff --git a/.github/workflows/ecosystem-surveyor.yml b/.github/workflows/ecosystem-surveyor.yml deleted file mode 100644 index 61706a7..0000000 --- a/.github/workflows/ecosystem-surveyor.yml +++ /dev/null @@ -1,248 +0,0 @@ -# Ecosystem Surveyor - Propagation + Context Injection -# Syncs workflows and metaprompts to org control centers with context -# -# Flow: -# 1. Read org-context.json for each child org -# 2. Inject context into metaprompts -# 3. Propagate to org control centers -# 4. Org control centers then propagate to their repos - -name: Ecosystem Surveyor - -on: - push: - branches: [main] - paths: - - '.github/metaprompts/**' - - '.github/org-context.json' - - '.github/workflows/ecosystem-*.yml' - - 'repository-files/**' - schedule: - # Weekly sync - Sundays at midnight - - cron: '0 0 * * 0' - workflow_dispatch: - inputs: - target_org: - description: 'Specific org to sync (leave empty for all)' - required: false - type: string - dry_run: - description: 'Dry run (no actual changes)' - required: false - type: boolean - default: false - -permissions: - contents: write - -jobs: - # ════════════════════════════════════════════════════════════════ - # SURVEY: Discover and prepare context for each org - # ════════════════════════════════════════════════════════════════ - survey: - name: Survey Organizations - runs-on: ubuntu-latest - outputs: - orgs: ${{ steps.discover.outputs.orgs }} - steps: - - uses: actions/checkout@v4 - - - name: Discover Managed Organizations - id: discover - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - echo "🔍 Discovering managed organizations..." - echo "" - echo "jbcom is PURELY control + documentation (no codebases)" - echo "Syncing to org control centers that manage actual codebases..." - echo "" - - # Read managed orgs from enterprise org-registry - MANAGED_ORGS=$(jq -r '.managedOrganizations[] | .org' .github/org-registry.json 2>/dev/null || echo "") - - # Build matrix of orgs with accessible control centers - ORGS="[]" - for ORG in $MANAGED_ORGS; do - CONTROL_CENTER=$(jq -r --arg org "$ORG" '.managedOrganizations[] | select(.org == $org) | .controlCenter' .github/org-registry.json) - - if gh repo view "$CONTROL_CENTER" > /dev/null 2>&1; then - ORGS=$(echo "$ORGS" | jq -c --arg org "$ORG" '. + [$org]') - echo " ✅ $CONTROL_CENTER" - else - echo " ⚠️ $CONTROL_CENTER not found or not accessible" - fi - done - - # Filter if specific org requested - if [ -n "${{ inputs.target_org }}" ]; then - ORGS=$(echo "$ORGS" | jq -c --arg org "${{ inputs.target_org }}" '[.[] | select(. == $org)]') - fi - - # Output compact JSON (single line) for GITHUB_OUTPUT - echo "orgs=$(echo "$ORGS" | jq -c '.')" >> $GITHUB_OUTPUT - echo "" - echo "📋 Will sync to: $ORGS" - - # ════════════════════════════════════════════════════════════════ - # PROPAGATE: Sync to each org control center - # ════════════════════════════════════════════════════════════════ - propagate: - name: Propagate to ${{ matrix.org }} - needs: survey - if: needs.survey.outputs.orgs != '[]' - runs-on: ubuntu-latest - strategy: - matrix: - org: ${{ fromJson(needs.survey.outputs.orgs) }} - fail-fast: false - steps: - - uses: actions/checkout@v4 - with: - path: source - - - name: Checkout Target - uses: actions/checkout@v4 - with: - repository: ${{ matrix.org }}/control-center - token: ${{ secrets.CI_GITHUB_TOKEN }} - path: target - - - name: Load Org Context - id: context - run: | - # Get org-specific context from enterprise registry - ORG="${{ matrix.org }}" - - ORG_CTX=$(jq --arg org "$ORG" '.managedOrganizations[] | select(.org == $org)' source/.github/org-registry.json 2>/dev/null || echo "{}") - - # Extract values - DOMAIN=$(echo "$ORG_CTX" | jq -r '.domain // "unknown"') - FOCUS=$(echo "$ORG_CTX" | jq -r '.focus | join(", ") // ""') - - echo "org=$ORG" >> $GITHUB_OUTPUT - echo "domain=$DOMAIN" >> $GITHUB_OUTPUT - echo "focus=$FOCUS" >> $GITHUB_OUTPUT - - echo "📋 Context for $ORG:" - echo " Domain: $DOMAIN" - echo " Focus: $FOCUS" - - - name: Inject Context into Metaprompts - run: | - ORG="${{ matrix.org }}" - DOMAIN="${{ steps.context.outputs.domain }}" - FOCUS="${{ steps.context.outputs.focus }}" - - mkdir -p target/.github/metaprompts - - # Process each metaprompt - for PROMPT in source/.github/metaprompts/*.md; do - FILENAME=$(basename "$PROMPT") - echo " Processing $FILENAME..." - - # Inject org context - sed -e "s|{{ORG_NAME}}|$ORG|g" \ - -e "s|{{DOMAIN}}|$DOMAIN|g" \ - -e "s|{{FOCUS_AREAS}}|$FOCUS|g" \ - "$PROMPT" > "target/.github/metaprompts/$FILENAME" - done - - echo "✅ Metaprompts injected with $ORG context" - - - name: Sync Workflows - run: | - mkdir -p target/.github/workflows - - # Copy ecosystem workflows - for WF in ecosystem-triage.yml ecosystem-control.yml ecosystem-merge.yml ecosystem-agents.yml; do - if [ -f "source/.github/workflows/$WF" ]; then - cp "source/.github/workflows/$WF" "target/.github/workflows/" - echo " ✅ $WF" - fi - done - - # Copy repository-files - if [ -d "source/repository-files/always-sync" ]; then - rsync -av --exclude='.git' source/repository-files/always-sync/ target/ - echo "✅ Repository files synced" - fi - - - name: Create Org Context File - run: | - ORG="${{ matrix.org }}" - DOMAIN="${{ steps.context.outputs.domain }}" - FOCUS="${{ steps.context.outputs.focus }}" - - cat > target/.github/org-context.json << EOF - { - "org": "$ORG", - "domain": "$DOMAIN", - "parentOrg": "jbcom", - "focus": $(echo "$FOCUS" | jq -R 'split(", ")'), - "syncedFrom": "jbcom/control-center", - "syncedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" - } - EOF - - echo "✅ Created org-context.json for $ORG" - - - name: Commit and Push - if: ${{ !inputs.dry_run }} - working-directory: target - run: | - git config user.name "Ecosystem Surveyor" - git config user.email "surveyor@${{ steps.context.outputs.domain }}" - - git add -A - - if git diff --staged --quiet; then - echo "✅ No changes to sync" - exit 0 - fi - - git commit -m "chore: sync from jbcom/control-center - - Synced: - - Metaprompts with org context - - Ecosystem workflows - - Repository files - - Source: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" - - git push - - echo "✅ Changes pushed to ${{ matrix.org }}/control-center" - - - name: Dry Run Report - if: ${{ inputs.dry_run }} - working-directory: target - run: | - echo "🔍 DRY RUN - Would sync:" - git status --short - - # ════════════════════════════════════════════════════════════════ - # REPORT: Summary of propagation - # ════════════════════════════════════════════════════════════════ - report: - name: Propagation Report - needs: [survey, propagate] - if: always() - runs-on: ubuntu-latest - steps: - - name: Generate Report - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - echo "╔════════════════════════════════════════════════════════════╗" - echo "║ 📊 ECOSYSTEM SURVEYOR REPORT ║" - echo "╚════════════════════════════════════════════════════════════╝" - echo "" - echo "Organizations synced: ${{ needs.survey.outputs.orgs }}" - echo "Dry run: ${{ inputs.dry_run || 'false' }}" - echo "" - echo "Files propagated:" - echo " • .github/metaprompts/*.md (with context injection)" - echo " • .github/workflows/ecosystem-*.yml" - echo " • .github/org-context.json" - echo " • repository-files/always-sync/**" diff --git a/.github/workflows/ecosystem-triage.yml b/.github/workflows/ecosystem-triage.yml deleted file mode 100644 index b569e7f..0000000 --- a/.github/workflows/ecosystem-triage.yml +++ /dev/null @@ -1,283 +0,0 @@ -# Ecosystem Triage - Feedback Queue Management -# Creates and maintains tracking issues as KV store for PR state -# -# Responsibilities: -# - Create tracking issue for each PR (the "fixed point") -# - Gather AI feedback from reviews/threads -# - Resolve outdated threads -# - Update tracking issue with synthesized state -# - Manage state labels (blocked/review/ready) -# -# Coordination: Tracking issue is the shared state between workflows - -name: Ecosystem Triage - -on: - # PR lifecycle - data plane events - pull_request: - types: [opened, synchronize, reopened, ready_for_review, closed] - pull_request_review: - types: [submitted, dismissed] - # Manual trigger via comment - issue_comment: - types: [created] - -permissions: - contents: read - pull-requests: write - issues: write - -concurrency: - group: triage-${{ github.event.pull_request.number || github.event.issue.number }} - cancel-in-progress: true - -jobs: - # ════════════════════════════════════════════════════════════════ - # TRIAGE: Gather → Resolve → Store in Tracking Issue (KV Store) - # ════════════════════════════════════════════════════════════════ - triage: - name: Triage - if: | - github.event_name == 'pull_request' || - github.event_name == 'pull_request_review' || - (github.event_name == 'issue_comment' && - contains(github.event.comment.body, '@cascade synthesize')) - runs-on: ubuntu-latest - outputs: - tracking_issue: ${{ steps.triage.outputs.tracking_issue }} - verdict: ${{ steps.triage.outputs.verdict }} - pr_number: ${{ steps.triage.outputs.pr_number }} - steps: - - name: Triage PR Feedback - id: triage - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - # ══════════════════════════════════════════════════════════ - # RESOLVE PR CONTEXT - # ══════════════════════════════════════════════════════════ - case "${{ github.event_name }}" in - issue_comment) - # Check if comment is on a PR - if [ -z "${{ github.event.issue.pull_request }}" ]; then - echo "Comment not on a PR, skipping" - exit 0 - fi - PR_NUM="${{ github.event.issue.number }}" - ;; - *) - PR_NUM="${{ github.event.pull_request.number }}" - ;; - esac - - [ -z "$PR_NUM" ] && echo "No PR number" && exit 0 - echo "pr_number=$PR_NUM" >> $GITHUB_OUTPUT - - # Handle PR closed event - if [ "${{ github.event.action }}" = "closed" ]; then - echo "🏁 PR #$PR_NUM closed - closing tracking issue..." - TRACKING=$(gh issue list --repo "${{ github.repository }}" --label "synthesis" --search "synthesis-pr-$PR_NUM in:title" --json number --jq '.[0].number // empty') - if [ -n "$TRACKING" ]; then - if [ "${{ github.event.pull_request.merged }}" = "true" ]; then - gh issue close $TRACKING --repo "${{ github.repository }}" --comment "✅ PR #$PR_NUM merged successfully." 2>/dev/null || true - else - gh issue close $TRACKING --repo "${{ github.repository }}" --comment "❌ PR #$PR_NUM closed without merge." 2>/dev/null || true - fi - fi - exit 0 - fi - - # Get PR details - PR_DATA=$(gh pr view $PR_NUM --repo "${{ github.repository }}" --json title,url,author,headRefName,isDraft 2>/dev/null || echo "{}") - PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // "Unknown"') - PR_URL=$(echo "$PR_DATA" | jq -r '.url // ""') - PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login // "unknown"') - PR_BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName // "unknown"') - IS_DRAFT=$(echo "$PR_DATA" | jq -r '.isDraft // false') - - # Skip drafts - [ "$IS_DRAFT" = "true" ] && echo "Draft PR, skipping" && exit 0 - - echo "╔════════════════════════════════════════════════════════════╗" - echo "║ 📦 TRIAGE: PR #$PR_NUM ║" - echo "╚════════════════════════════════════════════════════════════╝" - - OWNER="${{ github.repository_owner }}" - REPO="${{ github.event.repository.name }}" - AI_AGENTS="copilot|github-actions|coderabbitai|gemini|cursor|amazon-q|dependabot|bugbot|claude|sonarcloud|codacy|snyk" - - # ══════════════════════════════════════════════════════════ - # STEP 1: GET OR CREATE TRACKING ISSUE (KV key lookup) - # ══════════════════════════════════════════════════════════ - echo "" - echo "🔑 Step 1: KV Lookup..." - - ISSUE_KEY="synthesis-pr-$PR_NUM" - - # Ensure labels exist - for LABEL in "synthesis:7057FF" "queue-blocked:B60205" "queue-review:FBCA04" "queue-ready:0E8A16"; do - NAME="${LABEL%%:*}" - COLOR="${LABEL##*:}" - gh label create "$NAME" --color "$COLOR" --repo "${{ github.repository }}" 2>/dev/null || true - done - - # Search for existing tracking issue - TRACKING_ISSUE=$(gh issue list --repo "${{ github.repository }}" --label "synthesis" --search "$ISSUE_KEY in:title" --json number --jq '.[0].number // empty' 2>/dev/null) - - if [ -z "$TRACKING_ISSUE" ]; then - echo " Creating new tracking issue..." - - TRACKING_ISSUE=$(gh issue create --repo "${{ github.repository }}" \ - --title "$ISSUE_KEY: $PR_TITLE" \ - --body "# 🔬 Synthesis: PR #$PR_NUM - - > **PR:** $PR_URL - > **Author:** @$PR_AUTHOR - > **Branch:** \`$PR_BRANCH\` - - --- - - ## 📊 Queue State - - | Metric | Value | - |--------|-------| - | Status | Initializing... | - - --- - 🤖 Managed by Ecosystem Triage" \ - --label "synthesis" \ - 2>/dev/null | grep -oE '[0-9]+$') - - # Link tracking issue to PR - gh pr comment $PR_NUM --repo "${{ github.repository }}" --body "📦 **Tracking:** #$TRACKING_ISSUE" 2>/dev/null || true - echo " ✅ Created #$TRACKING_ISSUE" - else - echo " ✅ Found #$TRACKING_ISSUE" - fi - - echo "tracking_issue=$TRACKING_ISSUE" >> $GITHUB_OUTPUT - - # ══════════════════════════════════════════════════════════ - # STEP 2: GATHER AI FEEDBACK - # ══════════════════════════════════════════════════════════ - echo "" - echo "📥 Step 2: Gathering feedback..." - - QUERY='query($owner: String!, $repo: String!, $pr: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $pr) { - reviews(first: 100) { nodes { author { login } body state } } - reviewThreads(first: 100) { - nodes { - id isResolved isOutdated path line - comments(first: 3) { nodes { author { login } body } } - } - } - } - } - }' - - RESULT=$(gh api graphql -f query="$QUERY" -f owner="$OWNER" -f repo="$REPO" -F pr="$PR_NUM" 2>/dev/null || echo "{}") - - # Count feedback - REVIEW_COUNT=$(echo "$RESULT" | jq --arg a "$AI_AGENTS" '[.data.repository.pullRequest.reviews.nodes[]? | select(.author.login | test($a; "i"))] | length' 2>/dev/null || echo "0") - THREAD_COUNT=$(echo "$RESULT" | jq --arg a "$AI_AGENTS" '[.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved == false) | select(.comments.nodes[0].author.login | test($a; "i"))] | length' 2>/dev/null || echo "0") - CRITICAL_COUNT=$(echo "$RESULT" | jq --arg a "$AI_AGENTS" '[.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved == false) | select(.comments.nodes[0].author.login | test($a; "i")) | select(.comments.nodes[0].body | test("security|vulnerability|critical|breaking"; "i"))] | length' 2>/dev/null || echo "0") - - echo " Reviews: $REVIEW_COUNT | Threads: $THREAD_COUNT | Critical: $CRITICAL_COUNT" - - # ══════════════════════════════════════════════════════════ - # STEP 3: RESOLVE OUTDATED THREADS - # ══════════════════════════════════════════════════════════ - echo "" - echo "🧹 Step 3: Resolving outdated..." - - OUTDATED_IDS=$(echo "$RESULT" | jq -r --arg a "$AI_AGENTS" ' - .data.repository.pullRequest.reviewThreads.nodes[]? | - select(.isOutdated == true and .isResolved == false) | - select(.comments.nodes[0].author.login | test($a; "i")) | .id - ' 2>/dev/null || echo "") - - RESOLVED=0 - for TID in $OUTDATED_IDS; do - [ -z "$TID" ] && continue - gh api graphql -f query='mutation($id: ID!) { resolveReviewThread(input: {threadId: $id}) { thread { isResolved } } }' -f id="$TID" 2>/dev/null && RESOLVED=$((RESOLVED + 1)) - done - echo " ✅ Resolved: $RESOLVED" - - # ══════════════════════════════════════════════════════════ - # STEP 4: DETERMINE VERDICT - # ══════════════════════════════════════════════════════════ - echo "" - echo "⚖️ Step 4: Verdict..." - - if [ "$CRITICAL_COUNT" -gt 0 ]; then - VERDICT="BLOCKED"; VERDICT_EMOJI="🔴"; VERDICT_LABEL="queue-blocked" - elif [ "$THREAD_COUNT" -gt 0 ]; then - VERDICT="REVIEW"; VERDICT_EMOJI="🟡"; VERDICT_LABEL="queue-review" - else - VERDICT="READY"; VERDICT_EMOJI="✅"; VERDICT_LABEL="queue-ready" - fi - - echo " $VERDICT_EMOJI $VERDICT" - echo "verdict=$VERDICT" >> $GITHUB_OUTPUT - - # ══════════════════════════════════════════════════════════ - # STEP 5: BUILD FEEDBACK LIST - # ══════════════════════════════════════════════════════════ - FEEDBACK="" - if [ "$THREAD_COUNT" -gt 0 ]; then - FEEDBACK=$(echo "$RESULT" | jq -r --arg a "$AI_AGENTS" ' - [.data.repository.pullRequest.reviewThreads.nodes[]? | - select(.isResolved == false) | - select(.comments.nodes[0].author.login | test($a; "i"))] | - .[:10][] | - "- [ ] **\(.comments.nodes[0].author.login)** on `\(.path):\(.line // "?")`" - ' 2>/dev/null || echo "") - fi - [ -z "$FEEDBACK" ] && FEEDBACK="_No active feedback._" - - # ══════════════════════════════════════════════════════════ - # STEP 6: UPDATE TRACKING ISSUE (KV write) - # ══════════════════════════════════════════════════════════ - echo "" - echo "💾 Step 6: KV Write..." - - BODY="# 🔬 Synthesis: PR #$PR_NUM - - > **PR:** $PR_URL - > **Author:** @$PR_AUTHOR - > **Branch:** \`$PR_BRANCH\` - - --- - - ## 📊 Queue State - - | Metric | Value | - |--------|-------| - | Reviews | $REVIEW_COUNT | - | Active Threads | $THREAD_COUNT | - | Critical | $CRITICAL_COUNT | - | Resolved | $RESOLVED | - | Updated | $(date -u +%Y-%m-%dT%H:%M:%SZ) | - - ## 🎯 Verdict: $VERDICT_EMOJI $VERDICT - - ## 📝 Feedback Queue - - $FEEDBACK - - --- - - **Commands:** \`@cascade address\` · \`@cascade synthesize\` · \`@cascade merge\` - - 🤖 Ecosystem Triage" - - gh issue edit $TRACKING_ISSUE --repo "${{ github.repository }}" --body "$BODY" 2>/dev/null || true - - # Update labels - gh issue edit $TRACKING_ISSUE --repo "${{ github.repository }}" --remove-label "queue-blocked,queue-review,queue-ready" 2>/dev/null || true - gh issue edit $TRACKING_ISSUE --repo "${{ github.repository }}" --add-label "$VERDICT_LABEL" 2>/dev/null || true - - echo "" - echo "✅ Tracking issue #$TRACKING_ISSUE updated: $VERDICT" diff --git a/.github/workflows/jules-completion-handler.yml b/.github/workflows/jules-completion-handler.yml deleted file mode 100644 index 6ead857..0000000 --- a/.github/workflows/jules-completion-handler.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Jules Completion Handler - -# This workflow acts as a webhook handler for completed Jules sessions. -# It listens for a `repository_dispatch` event, which can be triggered -# by an external service when a Jules session finishes. - -on: - repository_dispatch: - types: [jules-session-complete] - -# No workflow-level permissions - all permissions defined at job level -permissions: {} - -jobs: - handle-completion: - name: Handle Jules Session Completion - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - issues: write - steps: - - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - token: ${{ secrets.CI_GITHUB_TOKEN }} - - - name: Post Completion Comment - env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - SESSION_ID: ${{ github.event.client_payload.session_id }} - OUTCOME: ${{ github.event.client_payload.outcome }} - PR_NUMBER: ${{ github.event.client_payload.pr_number }} - ISSUE_NUMBER: ${{ github.event.client_payload.issue_number }} - AGENT_TYPE: ${{ github.event.client_payload.agent_type }} - PR_URL: ${{ github.event.client_payload.pr_url }} - REPO: ${{ github.repository }} - run: | - set -eo pipefail - - # Determine emoji based on agent type - case "${AGENT_TYPE}" in - "bolt") EMOJI="⚡"; AGENT_NAME="Bolt (Performance)" ;; - "palette") EMOJI="🎨"; AGENT_NAME="Palette (UX)" ;; - "scribe") EMOJI="📚"; AGENT_NAME="Scribe (Docs)" ;; - "sentinel") EMOJI="🛡️"; AGENT_NAME="Sentinel (Security)" ;; - "bug-fixer") EMOJI="🐛"; AGENT_NAME="Bug Fixer" ;; - "cleanup") EMOJI="🧹"; AGENT_NAME="Cleanup" ;; - *) EMOJI="🤖"; AGENT_NAME="General" ;; - esac - - # Determine status icon and action - case "${OUTCOME}" in - "success") - STATUS_ICON="✅" - STATUS_TEXT="Completed successfully" - ;; - "failure") - STATUS_ICON="❌" - STATUS_TEXT="Failed - manual review needed" - ;; - "no_changes") - STATUS_ICON="ℹ️" - STATUS_TEXT="No changes needed" - ;; - "pr_created") - STATUS_ICON="🎉" - STATUS_TEXT="Pull request created" - ;; - *) - STATUS_ICON="❓" - STATUS_TEXT="Unknown outcome" - ;; - esac - - # Construct the comment body - COMMENT_BODY="### ${EMOJI} Jules ${AGENT_NAME} Session Complete - - | Field | Value | - |-------|-------| - | **Session ID** | \`${SESSION_ID}\` | - | **Agent** | ${AGENT_NAME} | - | **Outcome** | ${STATUS_ICON} ${STATUS_TEXT} | - " - - # Add PR link if available - if [[ -n "${PR_URL}" ]]; then - COMMENT_BODY="${COMMENT_BODY} - | **Pull Request** | [View PR](${PR_URL}) | - " - fi - - COMMENT_BODY="${COMMENT_BODY} - --- - > 🤖 *Automated by Jules* - > - > This PR will be automatically reviewed and merged if approved. - " - - if [[ -n "${PR_NUMBER}" ]]; then - # Comment on PR and add labels for triage - gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "$COMMENT_BODY" - gh pr edit "${PR_NUMBER}" --repo "${REPO}" --add-label "jules-pr,auto-triage" || true - echo "Posted completion comment to PR #${PR_NUMBER}" - elif [[ -n "${ISSUE_NUMBER}" ]]; then - # Comment on Issue - gh issue comment "${ISSUE_NUMBER}" --repo "${REPO}" --body "$COMMENT_BODY" - echo "Posted completion comment to Issue #${ISSUE_NUMBER}" - else - echo "::notice::No PR or issue number provided - completion logged only" - echo "Session completed: ${SESSION_ID} - ${OUTCOME}" - fi diff --git a/.github/workflows/jules-supervisor.yml b/.github/workflows/jules-supervisor.yml deleted file mode 100644 index e9c0ded..0000000 --- a/.github/workflows/jules-supervisor.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Jules Supervisor - -on: - workflow_dispatch: - -permissions: - contents: read - issues: write - pull-requests: write - -jobs: - supervise: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Setup Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - node-version: '22' - - - name: Run Jules Supervisor - env: - GOOGLE_JULES_API_KEY: ${{ secrets.GOOGLE_JULES_API_KEY }} - CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} - run: | - echo "Jules Supervisor - monitors and triages Jules sessions" - # TODO: Implement supervisor logic when needed diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml new file mode 100644 index 0000000..4c54a48 --- /dev/null +++ b/.github/workflows/review.yml @@ -0,0 +1,95 @@ +name: Review +# Consolidated PR review automation workflow +# Replaces: ai-reviewer, ecosystem-reviewer, ecosystem-reviewer-local + +on: + pull_request: + types: [opened, synchronize, reopened] + workflow_call: + inputs: + pr_number: + description: 'PR number to review' + required: true + type: string + repository: + description: 'Repository (owner/repo)' + required: true + type: string + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to review' + required: true + type: string + repository: + description: 'Repository (owner/repo)' + required: false + type: string + +env: + OLLAMA_HOST: ${{ vars.OLLAMA_HOST || 'https://ollama.com' }} + OLLAMA_MODEL: ${{ vars.OLLAMA_MODEL || 'glm-4.6:cloud' }} + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + quick-review: + runs-on: ubuntu-latest + steps: + - name: Review with control-center + id: review + run: | + PR_NUM="${{ inputs.pr_number || github.event.pull_request.number }}" + REPO="${{ inputs.repository || github.repository }}" + + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + -e OLLAMA_API_KEY="${OLLAMA_API_KEY}" \ + -e OLLAMA_HOST="${OLLAMA_HOST}" \ + -e OLLAMA_MODEL="${OLLAMA_MODEL}" \ + jbcom/control-center:latest reviewer review \ + --pr "$PR_NUM" \ + --repo "$REPO" \ + --output json > review-result.json + + echo "result<> $GITHUB_OUTPUT + cat review-result.json >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} + + - name: Post review comment + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const fs = require('fs'); + const review = JSON.parse(fs.readFileSync('review-result.json', 'utf8')); + + let body = `## 🤖 AI Code Review\n\n`; + body += `### Summary\n${review.summary}\n\n`; + + if (review.issues && review.issues.length > 0) { + body += `### Issues Found\n`; + review.issues.forEach(issue => { + const emoji = issue.severity === 'critical' ? '🔴' : + issue.severity === 'high' ? '🟠' : + issue.severity === 'medium' ? '🟡' : '🟢'; + body += `${emoji} **${issue.severity}**: ${issue.description}\n`; + if (issue.suggestion) { + body += ` - 💡 ${issue.suggestion}\n`; + } + }); + } + + body += `\n---\nReviewed by Control Center using ${process.env.OLLAMA_MODEL}`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request?.number || Number(context.payload.inputs.pr_number), + body: body + }); diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml new file mode 100644 index 0000000..4e5d390 --- /dev/null +++ b/.github/workflows/triage.yml @@ -0,0 +1,350 @@ +name: Triage +# Consolidated issue & PR triage workflow +# Replaces: ai-curator, ecosystem-curator, ecosystem-curator-local, ecosystem-triage +# +# INTELLIGENT ENTERPRISE HEALTH MANAGEMENT: +# - On push to main: Rebase ALL open PRs, resolve conflicts with autoheal +# - On PR events: Track and triage individual PRs +# - On schedule: Deduplicate issues, check PR health across repository +# - Prevents cascade of conflicts by keeping PRs up-to-date automatically + +on: + push: + branches: + - main + - master + issues: + types: [opened, reopened, labeled] + pull_request: + types: [opened, reopened, labeled, synchronize, ready_for_review] + issue_comment: + types: [created] + schedule: + - cron: '0 */6 * * *' # Every 6 hours for health checks + workflow_dispatch: + inputs: + operation: + description: 'Operation to perform' + required: true + type: choice + options: + - triage_issue + - track_pr + - deduplicate + - rebase_all_prs + - health_check + +permissions: + contents: write + issues: write + pull-requests: write + actions: write + +jobs: + # ============================================ + # ENTERPRISE HEALTH: Keep all PRs up-to-date + # ============================================ + rebase-prs-on-main-push: + runs-on: ubuntu-latest + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + steps: + - name: Get all open PRs + id: get-prs + run: | + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator list-prs \ + --repo ${{ github.repository }} \ + --output json > open-prs.json + + echo "count=$(jq length open-prs.json)" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Rebase and update each PR + if: steps.get-prs.outputs.count > 0 + run: | + echo "🔄 Found ${{ steps.get-prs.outputs.count }} open PRs to rebase" + + jq -c '.[]' open-prs.json | while read -r pr; do + PR_NUM=$(echo "$pr" | jq -r '.number') + PR_BRANCH=$(echo "$pr" | jq -r '.head.ref') + PR_TITLE=$(echo "$pr" | jq -r '.title') + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "🔄 Rebasing PR #$PR_NUM: $PR_TITLE" + echo " Branch: $PR_BRANCH" + + # Check if PR has conflicts + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator check-conflicts \ + --pr "$PR_NUM" \ + --repo ${{ github.repository }} \ + --output json > pr-$PR_NUM-status.json + + HAS_CONFLICTS=$(jq -r '.has_conflicts' pr-$PR_NUM-status.json) + IS_BEHIND=$(jq -r '.behind_base' pr-$PR_NUM-status.json) + + if [ "$IS_BEHIND" = "true" ] || [ "$HAS_CONFLICTS" = "true" ]; then + echo "⚠️ PR is behind base or has conflicts" + + # Attempt automatic rebase + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator rebase-pr \ + --pr "$PR_NUM" \ + --repo ${{ github.repository }} \ + --auto-resolve \ + --output json > pr-$PR_NUM-rebase.json + + REBASE_STATUS=$(jq -r '.status' pr-$PR_NUM-rebase.json) + + if [ "$REBASE_STATUS" = "success" ]; then + echo "✅ Successfully rebased and pushed" + + # Comment on PR + MESSAGE="🤖 **Automatic Rebase Complete** + + This PR was automatically rebased against the latest \`${{ github.ref_name }}\` after recent changes were merged. + + ✅ No conflicts detected + 🔄 Branch is now up-to-date + + --- + Performed by Control Center Triage" + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator comment \ + --pr "$PR_NUM" \ + --repo ${{ github.repository }} \ + --message "$MESSAGE" + + elif [ "$REBASE_STATUS" = "conflicts" ]; then + echo "⚠️ Conflicts detected, triggering autoheal" + + # Trigger autoheal workflow to resolve conflicts + gh workflow run autoheal.yml \ + -f operation=resolve_conflicts \ + -f pr_number="$PR_NUM" \ + -f repository="${{ github.repository }}" + + # Comment on PR + MESSAGE="🤖 **Automatic Rebase - Conflicts Detected** + + This PR has conflicts with the latest \`${{ github.ref_name }}\` branch. + + ⚠️ Conflicts found during automatic rebase + 🔧 Triggered autoheal workflow to resolve conflicts + ⏳ AI-powered conflict resolution in progress + + The autoheal workflow will analyze the conflicts and attempt to resolve them automatically. Check back soon for updates. + + --- + Performed by Control Center Triage" + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator comment \ + --pr "$PR_NUM" \ + --repo ${{ github.repository }} \ + --message "$MESSAGE" + + else + echo "❌ Rebase failed: $REBASE_STATUS" + + # Comment on PR with failure details + FAILURE_REASON=$(jq -r '.error' pr-$PR_NUM-rebase.json) + MESSAGE="🤖 **Automatic Rebase Failed** + + This PR could not be automatically rebased against \`${{ github.ref_name }}\`. + + ❌ Error: $FAILURE_REASON + + Please manually rebase your branch or resolve conflicts locally: + \`\`\`bash + git fetch origin + git rebase origin/${{ github.ref_name }} + git push --force-with-lease + \`\`\` + + --- + Performed by Control Center Triage" + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator comment \ + --pr "$PR_NUM" \ + --repo ${{ github.repository }} \ + --message "$MESSAGE" + fi + else + echo "✅ PR is up-to-date, no action needed" + fi + + echo "" + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate health report + if: always() + run: | + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator health-report \ + --repo ${{ github.repository }} \ + --output markdown > health-report.md + + cat health-report.md >> $GITHUB_STEP_SUMMARY + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ============================================ + # INDIVIDUAL PR TRIAGE + # ============================================ + triage-pr: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Triage PR + run: | + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator triage-pr \ + --pr ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if PR is behind base + if: github.event.action == 'opened' || github.event.action == 'reopened' + run: | + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator check-conflicts \ + --pr ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --output json > pr-status.json + + # Check if PR is behind and notify + IS_BEHIND=$(jq -r '.behind_base // false' pr-status.json) + if [ "$IS_BEHIND" = "true" ]; then + MESSAGE="ℹ️ This PR is behind the base branch. Consider rebasing to include the latest changes." + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator comment \ + --pr ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --message "$MESSAGE" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ============================================ + # ISSUE TRIAGE + # ============================================ + triage-issue: + runs-on: ubuntu-latest + if: github.event_name == 'issues' && github.event.action == 'opened' + steps: + - name: Triage new issue + run: | + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator triage \ + --issue ${{ github.event.issue.number }} \ + --repo ${{ github.repository }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ============================================ + # SCHEDULED HEALTH CHECKS + # ============================================ + scheduled-health-check: + runs-on: ubuntu-latest + if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.operation == 'health_check') + steps: + - name: Deduplicate issues + run: | + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator deduplicate \ + --repo ${{ github.repository }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check PR health + run: | + echo "🏥 Performing repository health check..." + + # Check all open PRs for staleness, conflicts, outdated branches + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator health-check \ + --repo ${{ github.repository }} \ + --check-prs \ + --check-stale \ + --check-conflicts \ + --auto-notify \ + --output json > health-check.json + + # Generate report + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + -v "${{ github.workspace }}:/workspace" \ + -w /workspace \ + jbcom/control-center:latest curator health-report \ + --input health-check.json \ + --output markdown > health-report.md + + cat health-report.md >> $GITHUB_STEP_SUMMARY + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Auto-rebase stale PRs + run: | + echo "🔄 Auto-rebasing PRs that are significantly behind..." + + # Get PRs that are >10 commits behind + jq -r '.prs[] | select(.behind_by > 10) | .number' health-check.json | while read -r PR_NUM; do + echo "Triggering rebase for PR #$PR_NUM" + + gh workflow run triage.yml \ + -f operation=rebase_all_prs + done + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ============================================ + # MANUAL OPERATIONS + # ============================================ + manual-operation: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' && inputs.operation != 'health_check' + steps: + - name: Execute operation + run: | + case "${{ inputs.operation }}" in + triage_issue) + echo "Manual issue triage not yet implemented" + ;; + track_pr) + echo "Manual PR tracking not yet implemented" + ;; + deduplicate) + docker run --rm \ + -e GITHUB_TOKEN="${GITHUB_TOKEN}" \ + jbcom/control-center:latest curator deduplicate --repo ${{ github.repository }} + ;; + rebase_all_prs) + echo "🔄 Manually triggered: Rebase all open PRs" + gh workflow run triage.yml # Trigger push event handler + ;; + *) + echo "Unknown operation: ${{ inputs.operation }}" + exit 1 + ;; + esac + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}