From ffe5bb4f8c6e8593e0187e03e5ad995b9e5093d9 Mon Sep 17 00:00:00 2001 From: BlackRoad Automation Date: Thu, 5 Feb 2026 17:56:39 -0600 Subject: [PATCH] chore(workflows): Deploy autonomous workflow system Deployed workflows: - autonomous-orchestrator.yml - autonomous-self-healer.yml - autonomous-cross-repo.yml - autonomous-dependency-manager.yml - autonomous-issue-manager.yml Co-Authored-By: BlackRoad Bot --- .github/workflows/autonomous-cross-repo.yml | 324 +++++++++ .../autonomous-dependency-manager.yml | 297 ++++++++ .../workflows/autonomous-issue-manager.yml | 399 +++++++++++ .github/workflows/autonomous-orchestrator.yml | 671 ++++++++++++++++++ .github/workflows/autonomous-self-healer.yml | 377 ++++++++++ 5 files changed, 2068 insertions(+) create mode 100644 .github/workflows/autonomous-cross-repo.yml create mode 100644 .github/workflows/autonomous-dependency-manager.yml create mode 100644 .github/workflows/autonomous-issue-manager.yml create mode 100644 .github/workflows/autonomous-orchestrator.yml create mode 100644 .github/workflows/autonomous-self-healer.yml diff --git a/.github/workflows/autonomous-cross-repo.yml b/.github/workflows/autonomous-cross-repo.yml new file mode 100644 index 000000000..cd34cddd3 --- /dev/null +++ b/.github/workflows/autonomous-cross-repo.yml @@ -0,0 +1,324 @@ +# .github/workflows/autonomous-cross-repo.yml +# Cross-repository coordination for synchronized changes + +name: "Autonomous Cross-Repo Coordinator" + +on: + push: + branches: [main, master] + paths: + - 'shared/**' + - 'packages/**' + - 'lib/**' + - '*.config.*' + workflow_dispatch: + inputs: + sync_type: + description: 'Type of sync' + required: true + type: choice + options: + - config + - dependencies + - workflows + - all + target_repos: + description: 'Target repos (comma-separated, or "all")' + required: false + default: 'all' + dry_run: + description: 'Dry run (no actual changes)' + required: false + default: true + type: boolean + +permissions: + contents: write + pull-requests: write + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.amundsonalexa.workers.dev + +jobs: + # ============================================ + # Identify Affected Repositories + # ============================================ + identify-repos: + name: "Identify Affected Repos" + runs-on: ubuntu-latest + outputs: + repos: ${{ steps.find.outputs.repos }} + sync_files: ${{ steps.changes.outputs.files }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Get Changed Files + id: changes + run: | + FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null | head -50 || echo "") + echo "files<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Find Related Repositories + id: find + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Default BlackRoad repos that should stay in sync + CORE_REPOS='[ + "BlackRoad-OS/blackroad-os-web", + "BlackRoad-OS/blackroad-os-docs", + "BlackRoad-OS/blackroad-cli", + "BlackRoad-OS/blackroad-agents", + "BlackRoad-OS/blackroad-os-mesh", + "BlackRoad-OS/blackroad-os-helper", + "BlackRoad-OS/blackroad-os-core" + ]' + + if [ "${{ github.event.inputs.target_repos }}" = "all" ] || [ -z "${{ github.event.inputs.target_repos }}" ]; then + REPOS="$CORE_REPOS" + else + # Convert comma-separated to JSON array + REPOS=$(echo '${{ github.event.inputs.target_repos }}' | jq -R 'split(",") | map(gsub("^\\s+|\\s+$";""))') + fi + + echo "repos=$REPOS" >> $GITHUB_OUTPUT + echo "Repos to sync: $REPOS" + + # ============================================ + # Sync Workflows + # ============================================ + sync-workflows: + name: "Sync Workflows" + needs: identify-repos + if: github.event.inputs.sync_type == 'workflows' || github.event.inputs.sync_type == 'all' || contains(needs.identify-repos.outputs.sync_files, '.github/workflows') + runs-on: ubuntu-latest + strategy: + matrix: + repo: ${{ fromJSON(needs.identify-repos.outputs.repos) }} + fail-fast: false + max-parallel: 5 + + steps: + - name: Checkout Source + uses: actions/checkout@v4 + with: + path: source + + - name: Checkout Target + uses: actions/checkout@v4 + with: + repository: ${{ matrix.repo }} + path: target + token: ${{ secrets.CROSS_REPO_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Sync Workflow Files + run: | + # Copy autonomous workflows + mkdir -p target/.github/workflows + + # Copy the orchestrator and self-healer + for workflow in autonomous-orchestrator.yml autonomous-self-healer.yml blackroad-agents.yml; do + if [ -f "source/.github/workflows-autonomous/$workflow" ]; then + cp "source/.github/workflows-autonomous/$workflow" "target/.github/workflows/" + elif [ -f "source/.github/workflows/$workflow" ]; then + cp "source/.github/workflows/$workflow" "target/.github/workflows/" + fi + done + + echo "Synced workflows to ${{ matrix.repo }}" + + - name: Create PR + if: github.event.inputs.dry_run != 'true' + working-directory: target + env: + GH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN || secrets.GITHUB_TOKEN }} + run: | + if [ -n "$(git status --porcelain)" ]; then + BRANCH="sync-workflows-$(date +%Y%m%d-%H%M%S)" + git config user.name "BlackRoad Cross-Repo Bot" + git config user.email "crossrepo@blackroad.ai" + + git checkout -b "$BRANCH" + git add -A + git commit -m "chore(workflows): Sync autonomous workflows from central repo + + Synced workflows: + - autonomous-orchestrator.yml + - autonomous-self-healer.yml + - blackroad-agents.yml + + Source: ${{ github.repository }} + + Co-Authored-By: BlackRoad Bot " + + git push -u origin "$BRANCH" + + gh pr create \ + --title "chore(workflows): Sync autonomous workflows" \ + --body "## Workflow Sync + + Synced autonomous workflows from central repository. + + **Source:** ${{ github.repository }} + **Sync Type:** workflows + + ### Changes + - Updated autonomous-orchestrator.yml + - Updated autonomous-self-healer.yml + - Updated blackroad-agents.yml + + --- + *Automated by BlackRoad Cross-Repo Coordinator*" \ + --label "automated,infrastructure" + else + echo "No workflow changes needed for ${{ matrix.repo }}" + fi + + # ============================================ + # Sync Configurations + # ============================================ + sync-config: + name: "Sync Configurations" + needs: identify-repos + if: github.event.inputs.sync_type == 'config' || github.event.inputs.sync_type == 'all' + runs-on: ubuntu-latest + strategy: + matrix: + repo: ${{ fromJSON(needs.identify-repos.outputs.repos) }} + fail-fast: false + max-parallel: 5 + + steps: + - name: Checkout Source + uses: actions/checkout@v4 + with: + path: source + + - name: Checkout Target + uses: actions/checkout@v4 + with: + repository: ${{ matrix.repo }} + path: target + token: ${{ secrets.CROSS_REPO_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Sync Config Files + run: | + # Sync common configs that should be consistent + SYNC_FILES=( + ".eslintrc.js" + ".prettierrc" + ".editorconfig" + "tsconfig.base.json" + ".github/CODEOWNERS" + ".github/ISSUE_TEMPLATE/bug_report.yml" + ".github/ISSUE_TEMPLATE/feature_request.yml" + ) + + for file in "${SYNC_FILES[@]}"; do + if [ -f "source/$file" ]; then + mkdir -p "target/$(dirname $file)" + cp "source/$file" "target/$file" + fi + done + + - name: Create PR + if: github.event.inputs.dry_run != 'true' + working-directory: target + env: + GH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN || secrets.GITHUB_TOKEN }} + run: | + if [ -n "$(git status --porcelain)" ]; then + BRANCH="sync-config-$(date +%Y%m%d-%H%M%S)" + git config user.name "BlackRoad Cross-Repo Bot" + git config user.email "crossrepo@blackroad.ai" + + git checkout -b "$BRANCH" + git add -A + git commit -m "chore(config): Sync configurations from central repo + + Co-Authored-By: BlackRoad Bot " + + git push -u origin "$BRANCH" + + gh pr create \ + --title "chore(config): Sync configurations" \ + --body "## Configuration Sync + + Synced common configurations from central repository. + + --- + *Automated by BlackRoad Cross-Repo Coordinator*" \ + --label "automated,config" + fi + + # ============================================ + # Sync Dependencies + # ============================================ + sync-deps: + name: "Sync Dependencies" + needs: identify-repos + if: github.event.inputs.sync_type == 'dependencies' || github.event.inputs.sync_type == 'all' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Analyze Dependencies + id: deps + run: | + # Extract common dependencies and their versions + if [ -f "package.json" ]; then + DEPS=$(jq -r '.dependencies // {} | to_entries[] | "\(.key)@\(.value)"' package.json | head -20) + echo "deps<> $GITHUB_OUTPUT + echo "$DEPS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Report Dependencies + run: | + echo "## Dependencies to Sync" + echo "${{ steps.deps.outputs.deps }}" + + # Log to coordination API + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/coordinate" \ + -H "Content-Type: application/json" \ + -d '{ + "action": "sync_deps", + "source": "${{ github.repository }}", + "repos": ${{ needs.identify-repos.outputs.repos }}, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || true + + # ============================================ + # Broadcast Changes + # ============================================ + broadcast: + name: "Broadcast Changes" + needs: [sync-workflows, sync-config, sync-deps] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Notify Coordination System + run: | + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/broadcast" \ + -H "Content-Type: application/json" \ + -d '{ + "event": "cross_repo_sync_complete", + "source": "${{ github.repository }}", + "sync_type": "${{ github.event.inputs.sync_type || 'auto' }}", + "repos": ${{ needs.identify-repos.outputs.repos || '[]' }}, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || echo "Broadcast queued" + + - name: Summary + run: | + echo "## Cross-Repo Sync Complete" + echo "- Source: ${{ github.repository }}" + echo "- Sync Type: ${{ github.event.inputs.sync_type || 'auto' }}" + echo "- Dry Run: ${{ github.event.inputs.dry_run || 'false' }}" diff --git a/.github/workflows/autonomous-dependency-manager.yml b/.github/workflows/autonomous-dependency-manager.yml new file mode 100644 index 000000000..8e2068579 --- /dev/null +++ b/.github/workflows/autonomous-dependency-manager.yml @@ -0,0 +1,297 @@ +# .github/workflows/autonomous-dependency-manager.yml +# Intelligent dependency management with bundled updates + +name: "Autonomous Dependency Manager" + +on: + schedule: + - cron: '0 3 * * 1' # Every Monday at 3 AM + workflow_dispatch: + inputs: + update_type: + description: 'Update type' + required: false + default: 'safe' + type: choice + options: + - safe # Patch versions only + - minor # Minor + patch + - major # All updates (risky) + - security # Security updates only + +permissions: + contents: write + pull-requests: write + security-events: read + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.amundsonalexa.workers.dev + +jobs: + # ============================================ + # Analyze Current State + # ============================================ + analyze: + name: "Analyze Dependencies" + runs-on: ubuntu-latest + outputs: + has_npm: ${{ steps.detect.outputs.npm }} + has_python: ${{ steps.detect.outputs.python }} + has_go: ${{ steps.detect.outputs.go }} + has_rust: ${{ steps.detect.outputs.rust }} + outdated_count: ${{ steps.check.outputs.count }} + security_issues: ${{ steps.security.outputs.count }} + + steps: + - uses: actions/checkout@v4 + + - name: Detect Package Managers + id: detect + run: | + echo "npm=$([[ -f package.json ]] && echo true || echo false)" >> $GITHUB_OUTPUT + echo "python=$([[ -f requirements.txt || -f pyproject.toml ]] && echo true || echo false)" >> $GITHUB_OUTPUT + echo "go=$([[ -f go.mod ]] && echo true || echo false)" >> $GITHUB_OUTPUT + echo "rust=$([[ -f Cargo.toml ]] && echo true || echo false)" >> $GITHUB_OUTPUT + + - name: Check Outdated (npm) + id: check + if: steps.detect.outputs.npm == 'true' + run: | + npm outdated --json > outdated.json 2>/dev/null || true + COUNT=$(jq 'length' outdated.json 2>/dev/null || echo 0) + echo "count=$COUNT" >> $GITHUB_OUTPUT + echo "Found $COUNT outdated packages" + + - name: Security Audit + id: security + run: | + ISSUES=0 + + if [ -f "package.json" ]; then + npm audit --json > npm-audit.json 2>/dev/null || true + NPM_VULNS=$(jq '.metadata.vulnerabilities | .low + .moderate + .high + .critical' npm-audit.json 2>/dev/null || echo 0) + ISSUES=$((ISSUES + NPM_VULNS)) + fi + + echo "count=$ISSUES" >> $GITHUB_OUTPUT + echo "Found $ISSUES security issues" + + # ============================================ + # Update npm Dependencies + # ============================================ + update-npm: + name: "Update npm Dependencies" + needs: analyze + if: needs.analyze.outputs.has_npm == 'true' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Dependencies + run: npm ci --ignore-scripts 2>/dev/null || npm install --ignore-scripts + + - name: Update Based on Type + id: update + run: | + UPDATE_TYPE="${{ github.event.inputs.update_type || 'safe' }}" + + case "$UPDATE_TYPE" in + safe) + # Only patch updates + npm update 2>/dev/null || true + ;; + minor) + # Minor and patch updates + npx npm-check-updates -u --target minor 2>/dev/null || npm update + npm install + ;; + major) + # All updates (risky) + npx npm-check-updates -u 2>/dev/null || true + npm install + ;; + security) + # Security updates only + npm audit fix 2>/dev/null || true + npm audit fix --force 2>/dev/null || true + ;; + esac + + # Check what changed + if [ -n "$(git status --porcelain package.json package-lock.json)" ]; then + echo "changes=true" >> $GITHUB_OUTPUT + else + echo "changes=false" >> $GITHUB_OUTPUT + fi + + - name: Run Tests + if: steps.update.outputs.changes == 'true' + id: test + continue-on-error: true + run: | + npm test 2>&1 && echo "result=passed" >> $GITHUB_OUTPUT || echo "result=failed" >> $GITHUB_OUTPUT + + - name: Create PR + if: steps.update.outputs.changes == 'true' && steps.test.outputs.result != 'failed' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH="deps/npm-update-$(date +%Y%m%d)" + git config user.name "BlackRoad Dependency Bot" + git config user.email "deps@blackroad.ai" + + # Check if branch already exists + if git ls-remote --exit-code origin "$BRANCH" 2>/dev/null; then + echo "Branch already exists, updating..." + git fetch origin "$BRANCH" + git checkout "$BRANCH" + git merge main --no-edit || true + else + git checkout -b "$BRANCH" + fi + + git add package.json package-lock.json + git commit -m "chore(deps): Update npm dependencies + + Update type: ${{ github.event.inputs.update_type || 'safe' }} + Tests: ${{ steps.test.outputs.result || 'not run' }} + + Co-Authored-By: BlackRoad Bot " || true + + git push -u origin "$BRANCH" --force + + # Check if PR already exists + EXISTING_PR=$(gh pr list --head "$BRANCH" --json number -q '.[0].number') + if [ -z "$EXISTING_PR" ]; then + gh pr create \ + --title "chore(deps): Weekly npm dependency updates" \ + --body "## Dependency Updates + + **Update Type:** ${{ github.event.inputs.update_type || 'safe' }} + **Test Status:** ${{ steps.test.outputs.result || 'not run' }} + + ### Changes + Updated npm dependencies according to the configured update strategy. + + ### Verification + - [ ] Tests pass + - [ ] Build succeeds + - [ ] No breaking changes + + --- + *Automated by BlackRoad Dependency Manager*" \ + --label "dependencies,automated" + fi + + # ============================================ + # Update Python Dependencies + # ============================================ + update-python: + name: "Update Python Dependencies" + needs: analyze + if: needs.analyze.outputs.has_python == 'true' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Update Dependencies + id: update + run: | + pip install pip-tools safety 2>/dev/null || true + + if [ -f "requirements.txt" ]; then + # Backup original + cp requirements.txt requirements.txt.bak + + # Update all packages + pip install --upgrade $(cat requirements.txt | grep -v "^#" | cut -d'=' -f1 | tr '\n' ' ') 2>/dev/null || true + + # Regenerate requirements with updated versions + pip freeze > requirements.txt.new + + # Check for changes + if ! diff -q requirements.txt requirements.txt.new > /dev/null 2>&1; then + mv requirements.txt.new requirements.txt + echo "changes=true" >> $GITHUB_OUTPUT + else + echo "changes=false" >> $GITHUB_OUTPUT + fi + fi + + - name: Run Tests + if: steps.update.outputs.changes == 'true' + id: test + continue-on-error: true + run: | + pip install -r requirements.txt + pytest 2>&1 && echo "result=passed" >> $GITHUB_OUTPUT || \ + python -m unittest discover 2>&1 && echo "result=passed" >> $GITHUB_OUTPUT || \ + echo "result=skipped" >> $GITHUB_OUTPUT + + - name: Create PR + if: steps.update.outputs.changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH="deps/python-update-$(date +%Y%m%d)" + git config user.name "BlackRoad Dependency Bot" + git config user.email "deps@blackroad.ai" + + git checkout -b "$BRANCH" + git add requirements.txt + git commit -m "chore(deps): Update Python dependencies + + Co-Authored-By: BlackRoad Bot " + git push -u origin "$BRANCH" + + gh pr create \ + --title "chore(deps): Weekly Python dependency updates" \ + --body "## Dependency Updates + + Updated Python dependencies. + + --- + *Automated by BlackRoad Dependency Manager*" \ + --label "dependencies,automated" + + # ============================================ + # Report Summary + # ============================================ + report: + name: "Generate Report" + needs: [analyze, update-npm, update-python] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Create Summary + run: | + echo "## Dependency Update Summary" + echo "" + echo "| Package Manager | Outdated | Security Issues |" + echo "|-----------------|----------|-----------------|" + echo "| npm | ${{ needs.analyze.outputs.outdated_count || 'N/A' }} | ${{ needs.analyze.outputs.security_issues || 'N/A' }} |" + + - name: Log to Memory + run: | + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/memory" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "event": "dependency_update", + "outdated_count": "${{ needs.analyze.outputs.outdated_count }}", + "security_issues": "${{ needs.analyze.outputs.security_issues }}", + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || true diff --git a/.github/workflows/autonomous-issue-manager.yml b/.github/workflows/autonomous-issue-manager.yml new file mode 100644 index 000000000..fbf98e7aa --- /dev/null +++ b/.github/workflows/autonomous-issue-manager.yml @@ -0,0 +1,399 @@ +# .github/workflows/autonomous-issue-manager.yml +# Autonomous issue creation, triage, and management + +name: "Autonomous Issue Manager" + +on: + issues: + types: [opened, edited, labeled, assigned] + issue_comment: + types: [created] + schedule: + - cron: '0 9 * * *' # Daily at 9 AM - stale check + workflow_run: + workflows: ["Autonomous Orchestrator", "Autonomous Self-Healer"] + types: [completed] + conclusions: [failure] + workflow_dispatch: + inputs: + action: + description: 'Action to perform' + required: true + type: choice + options: + - triage_all + - cleanup_stale + - generate_report + - create_health_issues + +permissions: + contents: read + issues: write + pull-requests: write + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.amundsonalexa.workers.dev + STALE_DAYS: 30 + CLOSE_DAYS: 7 + +jobs: + # ============================================ + # Smart Issue Triage + # ============================================ + triage: + name: "Smart Triage" + if: github.event_name == 'issues' && github.event.action == 'opened' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: AI Analysis + id: ai + run: | + TITLE="${{ github.event.issue.title }}" + BODY="${{ github.event.issue.body }}" + + # Call AI for smart categorization + ANALYSIS=$(curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/analyze-issue" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "'"$TITLE"'", + "body": "'"$(echo "$BODY" | head -c 2000 | jq -Rs .)"'", + "repo": "${{ github.repository }}" + }' 2>/dev/null || echo '{}') + + echo "analysis=$ANALYSIS" >> $GITHUB_OUTPUT + + # Parse AI response for labels + LABELS=$(echo "$ANALYSIS" | jq -r '.labels // [] | join(",")' 2>/dev/null || echo "") + PRIORITY=$(echo "$ANALYSIS" | jq -r '.priority // "normal"' 2>/dev/null || echo "normal") + ASSIGNEE=$(echo "$ANALYSIS" | jq -r '.assignee // ""' 2>/dev/null || echo "") + + echo "labels=$LABELS" >> $GITHUB_OUTPUT + echo "priority=$PRIORITY" >> $GITHUB_OUTPUT + echo "assignee=$ASSIGNEE" >> $GITHUB_OUTPUT + + - name: Keyword-Based Labeling + id: keywords + run: | + TITLE="${{ github.event.issue.title }}" + BODY="${{ github.event.issue.body }}" + TEXT="$TITLE $BODY" + LABELS="" + + # Type detection + echo "$TEXT" | grep -qi "bug\|error\|broken\|not working\|crash\|fail" && LABELS="$LABELS,bug" + echo "$TEXT" | grep -qi "feature\|add\|new\|enhance\|request" && LABELS="$LABELS,enhancement" + echo "$TEXT" | grep -qi "question\|how\|help\|what\|why" && LABELS="$LABELS,question" + echo "$TEXT" | grep -qi "doc\|documentation\|readme\|typo" && LABELS="$LABELS,documentation" + + # Area detection + echo "$TEXT" | grep -qi "security\|vulnerability\|cve\|auth" && LABELS="$LABELS,security" + echo "$TEXT" | grep -qi "performance\|slow\|memory\|cpu" && LABELS="$LABELS,performance" + echo "$TEXT" | grep -qi "ui\|frontend\|css\|style\|design" && LABELS="$LABELS,frontend" + echo "$TEXT" | grep -qi "api\|backend\|server\|database" && LABELS="$LABELS,backend" + echo "$TEXT" | grep -qi "ci\|deploy\|workflow\|action" && LABELS="$LABELS,infrastructure" + + # Priority detection + echo "$TEXT" | grep -qi "urgent\|critical\|asap\|important\|blocker" && LABELS="$LABELS,priority:high" + echo "$TEXT" | grep -qi "minor\|low\|when possible" && LABELS="$LABELS,priority:low" + + # Clean up labels + LABELS=$(echo "$LABELS" | sed 's/^,//' | sed 's/,,/,/g') + echo "labels=$LABELS" >> $GITHUB_OUTPUT + + - name: Apply Labels + uses: actions/github-script@v7 + with: + script: | + const aiLabels = '${{ steps.ai.outputs.labels }}'.split(',').filter(l => l); + const keywordLabels = '${{ steps.keywords.outputs.labels }}'.split(',').filter(l => l); + + // Merge and dedupe labels + const allLabels = [...new Set([...aiLabels, ...keywordLabels])].filter(l => l); + + if (allLabels.length > 0) { + // Ensure labels exist (create if not) + for (const label of allLabels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label + }); + } catch (e) { + // Label doesn't exist, create it + const colors = { + 'bug': 'd73a4a', + 'enhancement': 'a2eeef', + 'question': 'd876e3', + 'documentation': '0075ca', + 'security': 'b60205', + 'performance': 'fbca04', + 'frontend': '7057ff', + 'backend': '008672', + 'infrastructure': 'c5def5', + 'priority:high': 'b60205', + 'priority:low': 'c2e0c6' + }; + + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label, + color: colors[label] || '333333' + }).catch(() => {}); + } + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + labels: allLabels + }); + } + + - name: Welcome Response + uses: actions/github-script@v7 + with: + script: | + const labels = '${{ steps.keywords.outputs.labels }}'.split(',').filter(l => l); + const priority = '${{ steps.ai.outputs.priority }}'; + + let response = `Thanks for opening this issue! 👋\n\n`; + + // Add context based on type + if (labels.includes('bug')) { + response += `This has been identified as a **bug report**. `; + response += `To help us investigate:\n`; + response += `- What version are you using?\n`; + response += `- Can you provide steps to reproduce?\n`; + response += `- Any error messages or logs?\n\n`; + } else if (labels.includes('enhancement')) { + response += `This has been identified as a **feature request**. `; + response += `We'll review and prioritize accordingly.\n\n`; + } else if (labels.includes('question')) { + response += `This has been identified as a **question**. `; + response += `Check our [documentation](https://docs.blackroad.io) while you wait for a response.\n\n`; + } + + if (priority === 'high') { + response += `⚠️ **High priority** - This will be reviewed soon.\n\n`; + } + + response += `**Automated Labels Applied:** ${labels.length > 0 ? labels.map(l => '`' + l + '`').join(', ') : 'None'}\n\n`; + response += `---\n*Triaged by BlackRoad Autonomous Agent*`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: response + }); + + # ============================================ + # Stale Issue Cleanup + # ============================================ + stale-cleanup: + name: "Stale Cleanup" + if: github.event_name == 'schedule' || github.event.inputs.action == 'cleanup_stale' + runs-on: ubuntu-latest + + steps: + - name: Find Stale Issues + uses: actions/github-script@v7 + with: + script: | + const staleDays = parseInt('${{ env.STALE_DAYS }}'); + const closeDays = parseInt('${{ env.CLOSE_DAYS }}'); + const now = new Date(); + + // Get open issues + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + }); + + for (const issue of issues.data) { + // Skip PRs + if (issue.pull_request) continue; + + const updatedAt = new Date(issue.updated_at); + const daysSinceUpdate = Math.floor((now - updatedAt) / (1000 * 60 * 60 * 24)); + + const hasStaleLabel = issue.labels.some(l => l.name === 'stale'); + const isProtected = issue.labels.some(l => + ['pinned', 'security', 'priority:high', 'in-progress'].includes(l.name) + ); + + if (isProtected) continue; + + // Already marked stale - check if should close + if (hasStaleLabel && daysSinceUpdate >= closeDays) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed', + state_reason: 'not_planned' + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `This issue has been automatically closed due to inactivity.\n\nIf this is still relevant, please reopen it with additional context.\n\n---\n*Closed by BlackRoad Autonomous Agent*` + }); + + console.log(`Closed stale issue #${issue.number}`); + } + // Mark as stale + else if (!hasStaleLabel && daysSinceUpdate >= staleDays) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['stale'] + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `This issue has been automatically marked as **stale** because it has not had recent activity.\n\nIt will be closed in ${closeDays} days if no further activity occurs.\n\n---\n*Marked by BlackRoad Autonomous Agent*` + }); + + console.log(`Marked issue #${issue.number} as stale`); + } + } + + # ============================================ + # Auto-Create Issues from Failures + # ============================================ + failure-issue: + name: "Create Failure Issue" + if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure' + runs-on: ubuntu-latest + + steps: + - name: Check for Existing Issue + id: check + uses: actions/github-script@v7 + with: + script: | + // Search for existing issue about this workflow + const workflowName = '${{ github.event.workflow_run.name }}'; + const searchQuery = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open "[Automated] ${workflowName}" in:title`; + + const results = await github.rest.search.issuesAndPullRequests({ + q: searchQuery + }); + + core.setOutput('exists', results.data.total_count > 0); + if (results.data.total_count > 0) { + core.setOutput('issue_number', results.data.items[0].number); + } + + - name: Create or Update Issue + uses: actions/github-script@v7 + with: + script: | + const workflowName = '${{ github.event.workflow_run.name }}'; + const runId = '${{ github.event.workflow_run.id }}'; + const runUrl = '${{ github.event.workflow_run.html_url }}'; + const exists = '${{ steps.check.outputs.exists }}' === 'true'; + const existingNumber = '${{ steps.check.outputs.issue_number }}'; + + const body = `## Workflow Failure Detected + + **Workflow:** ${workflowName} + **Run ID:** ${runId} + **Run URL:** ${runUrl} + **Time:** ${new Date().toISOString()} + + ### Details + The autonomous orchestrator detected a failure in the ${workflowName} workflow. + + ### Suggested Actions + 1. Review the [workflow run logs](${runUrl}) + 2. Check recent commits for potential causes + 3. Run the self-healer workflow if appropriate + + --- + *Created by BlackRoad Autonomous Agent*`; + + if (exists) { + // Add comment to existing issue + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parseInt(existingNumber), + body: `### New Failure Detected\n\n**Run:** ${runUrl}\n**Time:** ${new Date().toISOString()}` + }); + } else { + // Create new issue + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Automated] ${workflowName} Workflow Failure`, + body: body, + labels: ['bug', 'automated', 'ci-failure'] + }); + } + + # ============================================ + # Generate Report + # ============================================ + report: + name: "Generate Issue Report" + if: github.event.inputs.action == 'generate_report' + runs-on: ubuntu-latest + + steps: + - name: Generate Statistics + uses: actions/github-script@v7 + with: + script: | + // Get all issues + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'all', + per_page: 100 + }); + + const stats = { + total: issues.data.length, + open: issues.data.filter(i => i.state === 'open' && !i.pull_request).length, + closed: issues.data.filter(i => i.state === 'closed' && !i.pull_request).length, + bugs: issues.data.filter(i => i.labels.some(l => l.name === 'bug')).length, + enhancements: issues.data.filter(i => i.labels.some(l => l.name === 'enhancement')).length, + stale: issues.data.filter(i => i.labels.some(l => l.name === 'stale')).length + }; + + console.log('Issue Statistics:', stats); + + // Create summary issue + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Report] Issue Statistics - ${new Date().toISOString().split('T')[0]}`, + body: `## Issue Statistics Report + + | Metric | Count | + |--------|-------| + | Total Issues | ${stats.total} | + | Open | ${stats.open} | + | Closed | ${stats.closed} | + | Bugs | ${stats.bugs} | + | Enhancements | ${stats.enhancements} | + | Stale | ${stats.stale} | + + --- + *Generated by BlackRoad Autonomous Agent*`, + labels: ['report', 'automated'] + }); diff --git a/.github/workflows/autonomous-orchestrator.yml b/.github/workflows/autonomous-orchestrator.yml new file mode 100644 index 000000000..29af55137 --- /dev/null +++ b/.github/workflows/autonomous-orchestrator.yml @@ -0,0 +1,671 @@ +# .github/workflows/autonomous-orchestrator.yml +# Master coordinator for all autonomous agents +# Drop this in any repo for full autonomous operation + +name: "Autonomous Orchestrator" + +on: + push: + branches: [main, master, develop] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + issues: + types: [opened, labeled] + issue_comment: + types: [created] + schedule: + - cron: '0 */4 * * *' # Every 4 hours + workflow_dispatch: + inputs: + mode: + description: 'Operation mode' + required: false + default: 'auto' + type: choice + options: + - auto + - aggressive + - conservative + - audit-only + force_deploy: + description: 'Force deployment' + required: false + default: false + type: boolean + +permissions: + contents: write + pull-requests: write + issues: write + actions: write + security-events: write + checks: write + +concurrency: + group: autonomous-${{ github.repository }}-${{ github.ref }} + cancel-in-progress: false + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.amundsonalexa.workers.dev + MEMORY_ENABLED: true + AUTO_MERGE: true + AUTO_FIX: true + +jobs: + # ============================================ + # Stage 1: Intelligence Gathering + # ============================================ + analyze: + name: "Analyze Repository" + runs-on: ubuntu-latest + outputs: + project_type: ${{ steps.detect.outputs.type }} + has_tests: ${{ steps.detect.outputs.has_tests }} + has_build: ${{ steps.detect.outputs.has_build }} + health_score: ${{ steps.health.outputs.score }} + priority: ${{ steps.priority.outputs.level }} + action_plan: ${{ steps.plan.outputs.actions }} + memory_context: ${{ steps.memory.outputs.context }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect Project Type + id: detect + run: | + TYPE="unknown" + HAS_TESTS="false" + HAS_BUILD="false" + + # Node.js + if [ -f "package.json" ]; then + TYPE="nodejs" + grep -q '"test"' package.json && HAS_TESTS="true" + grep -q '"build"' package.json && HAS_BUILD="true" + # Python + elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ] || [ -f "setup.py" ]; then + TYPE="python" + [ -d "tests" ] || [ -d "test" ] && HAS_TESTS="true" + # Go + elif [ -f "go.mod" ]; then + TYPE="go" + find . -name "*_test.go" | head -1 | grep -q . && HAS_TESTS="true" + # Rust + elif [ -f "Cargo.toml" ]; then + TYPE="rust" + HAS_TESTS="true" + HAS_BUILD="true" + # Salesforce + elif [ -f "sfdx-project.json" ]; then + TYPE="salesforce" + # Static site + elif [ -f "index.html" ]; then + TYPE="static" + # Cloudflare Worker + elif [ -f "wrangler.toml" ]; then + TYPE="cloudflare-worker" + HAS_BUILD="true" + fi + + echo "type=$TYPE" >> $GITHUB_OUTPUT + echo "has_tests=$HAS_TESTS" >> $GITHUB_OUTPUT + echo "has_build=$HAS_BUILD" >> $GITHUB_OUTPUT + echo "Detected: $TYPE (tests=$HAS_TESTS, build=$HAS_BUILD)" + + - name: Calculate Health Score + id: health + run: | + SCORE=100 + + # Check for common issues + [ ! -f "README.md" ] && SCORE=$((SCORE - 10)) + [ ! -f ".gitignore" ] && SCORE=$((SCORE - 5)) + [ ! -d ".github/workflows" ] && SCORE=$((SCORE - 15)) + + # Security checks + grep -rn "password\s*=" --include="*.js" --include="*.ts" --include="*.py" . 2>/dev/null | grep -v node_modules && SCORE=$((SCORE - 20)) + grep -rn "api_key\s*=" --include="*.js" --include="*.ts" --include="*.py" . 2>/dev/null | grep -v node_modules && SCORE=$((SCORE - 20)) + + # Stale checks + LAST_COMMIT=$(git log -1 --format=%ct 2>/dev/null || echo 0) + NOW=$(date +%s) + DAYS_SINCE=$(( (NOW - LAST_COMMIT) / 86400 )) + [ $DAYS_SINCE -gt 90 ] && SCORE=$((SCORE - 10)) + + [ $SCORE -lt 0 ] && SCORE=0 + echo "score=$SCORE" >> $GITHUB_OUTPUT + echo "Health Score: $SCORE/100" + + - name: Determine Priority + id: priority + run: | + PRIORITY="normal" + + # High priority triggers + if echo "${{ github.event.issue.labels.*.name }}" | grep -qE "critical|urgent|security"; then + PRIORITY="critical" + elif echo "${{ github.event.issue.labels.*.name }}" | grep -qE "high|important"; then + PRIORITY="high" + elif [ "${{ github.event_name }}" = "schedule" ]; then + PRIORITY="background" + fi + + echo "level=$PRIORITY" >> $GITHUB_OUTPUT + echo "Priority: $PRIORITY" + + - name: Fetch Memory Context + id: memory + run: | + # Try to get memory from BlackRoad API + CONTEXT=$(curl -s -f "${{ env.BLACKROAD_AGENT_API }}/memory/${{ github.repository }}" 2>/dev/null || echo '{}') + echo "context=$CONTEXT" >> $GITHUB_OUTPUT + + - name: Create Action Plan + id: plan + run: | + ACTIONS="[]" + + # Build action list based on context + case "${{ github.event_name }}" in + push) + ACTIONS='["test","build","security_scan","quality_check"]' + ;; + pull_request) + ACTIONS='["test","build","code_review","security_scan","auto_merge"]' + ;; + issues) + ACTIONS='["triage","assign","respond"]' + ;; + schedule) + ACTIONS='["health_check","dependency_update","stale_cleanup","security_audit"]' + ;; + *) + ACTIONS='["analyze"]' + ;; + esac + + echo "actions=$ACTIONS" >> $GITHUB_OUTPUT + + # ============================================ + # Stage 2: Test & Build + # ============================================ + test-and-build: + name: "Test & Build" + needs: analyze + runs-on: ubuntu-latest + if: needs.analyze.outputs.project_type != 'unknown' + outputs: + test_result: ${{ steps.test.outputs.result }} + build_result: ${{ steps.build.outputs.result }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup Environment + run: | + case "${{ needs.analyze.outputs.project_type }}" in + nodejs) + echo "Setting up Node.js..." + ;; + python) + echo "Setting up Python..." + pip install pytest pytest-cov 2>/dev/null || true + ;; + go) + echo "Go is pre-installed" + ;; + rust) + echo "Installing Rust..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + ;; + esac + + - name: Install Dependencies + run: | + case "${{ needs.analyze.outputs.project_type }}" in + nodejs) + npm ci --ignore-scripts 2>/dev/null || npm install --ignore-scripts 2>/dev/null || true + ;; + python) + [ -f "requirements.txt" ] && pip install -r requirements.txt 2>/dev/null || true + [ -f "pyproject.toml" ] && pip install -e . 2>/dev/null || true + ;; + go) + go mod download 2>/dev/null || true + ;; + rust) + cargo fetch 2>/dev/null || true + ;; + esac + + - name: Run Tests + id: test + continue-on-error: true + run: | + RESULT="skipped" + + if [ "${{ needs.analyze.outputs.has_tests }}" = "true" ]; then + case "${{ needs.analyze.outputs.project_type }}" in + nodejs) + npm test 2>&1 && RESULT="passed" || RESULT="failed" + ;; + python) + pytest -v 2>&1 && RESULT="passed" || python -m unittest discover 2>&1 && RESULT="passed" || RESULT="failed" + ;; + go) + go test ./... 2>&1 && RESULT="passed" || RESULT="failed" + ;; + rust) + cargo test 2>&1 && RESULT="passed" || RESULT="failed" + ;; + esac + fi + + echo "result=$RESULT" >> $GITHUB_OUTPUT + echo "Test result: $RESULT" + + - name: Run Build + id: build + continue-on-error: true + run: | + RESULT="skipped" + + if [ "${{ needs.analyze.outputs.has_build }}" = "true" ]; then + case "${{ needs.analyze.outputs.project_type }}" in + nodejs) + npm run build 2>&1 && RESULT="passed" || RESULT="failed" + ;; + rust) + cargo build --release 2>&1 && RESULT="passed" || RESULT="failed" + ;; + cloudflare-worker) + npx wrangler build 2>/dev/null && RESULT="passed" || RESULT="skipped" + ;; + esac + fi + + echo "result=$RESULT" >> $GITHUB_OUTPUT + echo "Build result: $RESULT" + + # ============================================ + # Stage 3: Security & Quality + # ============================================ + security-scan: + name: "Security Scan" + needs: analyze + runs-on: ubuntu-latest + outputs: + vulnerabilities: ${{ steps.scan.outputs.vulns }} + severity: ${{ steps.scan.outputs.max_severity }} + + steps: + - uses: actions/checkout@v4 + + - name: Run Security Scanners + id: scan + run: | + VULNS=0 + MAX_SEVERITY="none" + + # Scan for secrets + echo "Scanning for secrets..." + if grep -rn --include="*.js" --include="*.ts" --include="*.py" --include="*.json" \ + -E "(password|secret|api_key|token)\s*[:=]\s*['\"][^'\"]+['\"]" . 2>/dev/null | \ + grep -v node_modules | grep -v ".git" | head -5; then + VULNS=$((VULNS + 1)) + MAX_SEVERITY="critical" + fi + + # Check for common vulnerabilities + echo "Checking for common vulnerabilities..." + if [ -f "package.json" ]; then + npm audit --json 2>/dev/null | jq -r '.metadata.vulnerabilities | to_entries[] | select(.value > 0)' && VULNS=$((VULNS + 1)) + fi + + echo "vulns=$VULNS" >> $GITHUB_OUTPUT + echo "max_severity=$MAX_SEVERITY" >> $GITHUB_OUTPUT + + - name: Auto-fix Security Issues + if: env.AUTO_FIX == 'true' && steps.scan.outputs.vulns != '0' + run: | + echo "Attempting auto-fix..." + + # Try to fix npm vulnerabilities + if [ -f "package.json" ]; then + npm audit fix 2>/dev/null || true + fi + + # Check if changes were made + if [ -n "$(git status --porcelain)" ]; then + git config user.name "BlackRoad Bot" + git config user.email "bot@blackroad.ai" + git add -A + git commit -m "fix(security): Auto-fix security vulnerabilities + + Automated security fixes applied by BlackRoad Autonomous Agent. + + Co-Authored-By: BlackRoad Bot " + git push || echo "Push failed - may need PR" + fi + + # ============================================ + # Stage 4: Code Review (PRs only) + # ============================================ + code-review: + name: "AI Code Review" + needs: [analyze, test-and-build] + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Changed Files + id: changed + run: | + FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} 2>/dev/null | head -50) + echo "files<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: AI Code Analysis + id: ai_review + run: | + # Call BlackRoad AI for code review + REVIEW=$(curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/review" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "pr_number": ${{ github.event.pull_request.number }}, + "files": ${{ toJSON(steps.changed.outputs.files) }}, + "test_result": "${{ needs.test-and-build.outputs.test_result }}", + "build_result": "${{ needs.test-and-build.outputs.build_result }}" + }' 2>/dev/null || echo "Review completed") + + echo "AI Review: $REVIEW" + + - name: Post Review Comment + uses: actions/github-script@v7 + with: + script: | + const testResult = '${{ needs.test-and-build.outputs.test_result }}'; + const buildResult = '${{ needs.test-and-build.outputs.build_result }}'; + const healthScore = '${{ needs.analyze.outputs.health_score }}'; + + let status = ''; + if (testResult === 'passed' && buildResult !== 'failed') { + status = '### Status: Ready to Merge'; + } else if (testResult === 'failed') { + status = '### Status: Tests Failing - Needs Fix'; + } else { + status = '### Status: Review Needed'; + } + + const body = `## Autonomous Agent Review + + ${status} + + | Check | Result | + |-------|--------| + | Tests | ${testResult === 'passed' ? 'Passed' : testResult === 'failed' ? 'Failed' : 'Skipped'} | + | Build | ${buildResult === 'passed' ? 'Passed' : buildResult === 'failed' ? 'Failed' : 'Skipped'} | + | Health Score | ${healthScore}/100 | + + --- + *Autonomous review by BlackRoad Agent*`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: body + }); + + # ============================================ + # Stage 5: Auto-Merge + # ============================================ + auto-merge: + name: "Auto-Merge" + needs: [analyze, test-and-build, security-scan, code-review] + if: | + github.event_name == 'pull_request' && + needs.test-and-build.outputs.test_result != 'failed' && + needs.test-and-build.outputs.build_result != 'failed' && + needs.security-scan.outputs.severity != 'critical' + runs-on: ubuntu-latest + + steps: + - name: Enable Auto-Merge + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Enabling auto-merge for PR #${{ github.event.pull_request.number }}" + + # Try multiple merge strategies + gh pr merge ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --auto \ + --squash \ + --delete-branch 2>/dev/null || \ + gh pr merge ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --squash 2>/dev/null || \ + echo "Auto-merge queued - waiting for required checks" + + # ============================================ + # Stage 6: Auto-Deploy + # ============================================ + auto-deploy: + name: "Auto-Deploy" + needs: [analyze, test-and-build, security-scan] + if: | + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.test-and-build.outputs.test_result != 'failed' && + needs.security-scan.outputs.severity != 'critical' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Determine Deploy Target + id: target + run: | + TARGET="none" + + # Check for deployment configs + [ -f "wrangler.toml" ] && TARGET="cloudflare" + [ -f "vercel.json" ] && TARGET="vercel" + [ -f "railway.toml" ] && TARGET="railway" + [ -f "Dockerfile" ] && TARGET="docker" + + echo "target=$TARGET" >> $GITHUB_OUTPUT + + - name: Deploy to Cloudflare + if: steps.target.outputs.target == 'cloudflare' + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + run: | + if [ -n "$CLOUDFLARE_API_TOKEN" ]; then + npx wrangler deploy 2>/dev/null || echo "Cloudflare deploy skipped" + fi + + - name: Deploy to Vercel + if: steps.target.outputs.target == 'vercel' + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + run: | + if [ -n "$VERCEL_TOKEN" ]; then + npx vercel --prod --token=$VERCEL_TOKEN 2>/dev/null || echo "Vercel deploy skipped" + fi + + # ============================================ + # Stage 7: Memory Persistence + # ============================================ + persist-memory: + name: "Persist Memory" + needs: [test-and-build, security-scan] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Save Run Memory + run: | + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/memory" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "run_id": "${{ github.run_id }}", + "event": "${{ github.event_name }}", + "results": { + "test": "${{ needs.test-and-build.outputs.test_result }}", + "build": "${{ needs.test-and-build.outputs.build_result }}", + "security": "${{ needs.security-scan.outputs.severity }}" + }, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || echo "Memory save queued" + + # ============================================ + # Stage 8: Issue Triage (Issues only) + # ============================================ + issue-triage: + name: "Issue Triage" + needs: analyze + if: github.event_name == 'issues' && github.event.action == 'opened' + runs-on: ubuntu-latest + + steps: + - name: AI Issue Analysis + id: analyze + run: | + TITLE="${{ github.event.issue.title }}" + BODY="${{ github.event.issue.body }}" + + # Determine labels based on content + LABELS="" + + echo "$TITLE $BODY" | grep -qi "bug\|error\|broken\|fix" && LABELS="$LABELS,bug" + echo "$TITLE $BODY" | grep -qi "feature\|add\|new\|enhance" && LABELS="$LABELS,enhancement" + echo "$TITLE $BODY" | grep -qi "question\|how\|help" && LABELS="$LABELS,question" + echo "$TITLE $BODY" | grep -qi "security\|vulnerability\|cve" && LABELS="$LABELS,security" + echo "$TITLE $BODY" | grep -qi "urgent\|critical\|asap" && LABELS="$LABELS,priority:high" + + LABELS=$(echo "$LABELS" | sed 's/^,//') + echo "labels=$LABELS" >> $GITHUB_OUTPUT + + - name: Apply Labels + if: steps.analyze.outputs.labels != '' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + for label in $(echo "${{ steps.analyze.outputs.labels }}" | tr ',' ' '); do + gh issue edit ${{ github.event.issue.number }} --add-label "$label" 2>/dev/null || true + done + + - name: Auto-Respond + uses: actions/github-script@v7 + with: + script: | + const labels = '${{ steps.analyze.outputs.labels }}'.split(',').filter(l => l); + + let response = `Thanks for opening this issue!\n\n`; + response += `**Automated Triage:**\n`; + if (labels.length > 0) { + response += `- Labels applied: ${labels.map(l => '`' + l + '`').join(', ')}\n`; + } + response += `\nA team member will review this shortly. In the meantime:\n`; + response += `- Check if there's a similar issue already open\n`; + response += `- Provide additional context if available\n\n`; + response += `*Triaged by BlackRoad Autonomous Agent*`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: response + }); + + # ============================================ + # Stage 9: Scheduled Maintenance + # ============================================ + maintenance: + name: "Scheduled Maintenance" + needs: analyze + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Update Dependencies + run: | + if [ -f "package.json" ]; then + # Check for outdated packages + npm outdated --json > outdated.json 2>/dev/null || true + + # Auto-update patch versions + npm update 2>/dev/null || true + + if [ -n "$(git status --porcelain package-lock.json)" ]; then + git config user.name "BlackRoad Bot" + git config user.email "bot@blackroad.ai" + git add package.json package-lock.json + git commit -m "chore(deps): Auto-update dependencies + + Automated dependency updates by BlackRoad Agent. + + Co-Authored-By: BlackRoad Bot " + git push || echo "Would create PR for updates" + fi + fi + + - name: Clean Stale Branches + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # List merged branches older than 30 days + git fetch --all --prune + for branch in $(git branch -r --merged origin/main | grep -v main | grep -v HEAD); do + LAST_COMMIT=$(git log -1 --format=%ct "$branch" 2>/dev/null || echo 0) + NOW=$(date +%s) + DAYS_OLD=$(( (NOW - LAST_COMMIT) / 86400 )) + + if [ $DAYS_OLD -gt 30 ]; then + BRANCH_NAME=$(echo "$branch" | sed 's|origin/||') + echo "Deleting stale branch: $BRANCH_NAME ($DAYS_OLD days old)" + git push origin --delete "$BRANCH_NAME" 2>/dev/null || true + fi + done + + - name: Health Report + uses: actions/github-script@v7 + with: + script: | + const healthScore = '${{ needs.analyze.outputs.health_score }}'; + + // Only create issue if health is poor + if (parseInt(healthScore) < 70) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Automated] Repository Health Alert (Score: ${healthScore}/100)`, + body: `## Repository Health Report + + **Current Health Score:** ${healthScore}/100 + + The autonomous agent has detected potential issues with this repository. + + ### Recommended Actions + - Review and address any security warnings + - Update outdated dependencies + - Add missing documentation + + --- + *Generated by BlackRoad Autonomous Agent*`, + labels: ['maintenance', 'automated'] + }); + } diff --git a/.github/workflows/autonomous-self-healer.yml b/.github/workflows/autonomous-self-healer.yml new file mode 100644 index 000000000..8ec60a91e --- /dev/null +++ b/.github/workflows/autonomous-self-healer.yml @@ -0,0 +1,377 @@ +# .github/workflows/autonomous-self-healer.yml +# Self-healing agent that automatically fixes common issues + +name: "Autonomous Self-Healer" + +on: + workflow_run: + workflows: ["Autonomous Orchestrator", "CI", "Test"] + types: [completed] + conclusions: [failure] + schedule: + - cron: '30 */6 * * *' # Every 6 hours, offset from orchestrator + workflow_dispatch: + inputs: + fix_type: + description: 'Type of fix to attempt' + required: false + default: 'all' + type: choice + options: + - all + - tests + - build + - lint + - deps + - security + +permissions: + contents: write + pull-requests: write + actions: read + checks: read + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.amundsonalexa.workers.dev + MAX_FIX_ATTEMPTS: 3 + +jobs: + # ============================================ + # Diagnose the Failure + # ============================================ + diagnose: + name: "Diagnose Failure" + runs-on: ubuntu-latest + outputs: + failure_type: ${{ steps.analyze.outputs.type }} + failure_details: ${{ steps.analyze.outputs.details }} + fixable: ${{ steps.analyze.outputs.fixable }} + fix_strategy: ${{ steps.strategy.outputs.approach }} + + steps: + - uses: actions/checkout@v4 + + - name: Get Failed Run Logs + id: logs + if: github.event.workflow_run.id + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Download workflow run logs + gh run view ${{ github.event.workflow_run.id }} --log 2>/dev/null > run_logs.txt || true + echo "logs_retrieved=true" >> $GITHUB_OUTPUT + + - name: Analyze Failure + id: analyze + run: | + TYPE="unknown" + DETAILS="" + FIXABLE="false" + + if [ -f "run_logs.txt" ]; then + # Test failures + if grep -qi "test.*fail\|jest.*fail\|pytest.*fail\|assertion.*error" run_logs.txt; then + TYPE="test_failure" + DETAILS=$(grep -i "fail\|error" run_logs.txt | head -10) + FIXABLE="maybe" + # Build failures + elif grep -qi "build.*fail\|compile.*error\|typescript.*error" run_logs.txt; then + TYPE="build_failure" + DETAILS=$(grep -i "error" run_logs.txt | head -10) + FIXABLE="maybe" + # Lint failures + elif grep -qi "lint.*error\|eslint.*error\|prettier" run_logs.txt; then + TYPE="lint_failure" + FIXABLE="true" + # Dependency failures + elif grep -qi "npm.*err\|pip.*error\|dependency.*not found\|module.*not found" run_logs.txt; then + TYPE="dependency_failure" + DETAILS=$(grep -i "not found\|missing" run_logs.txt | head -5) + FIXABLE="true" + # Security failures + elif grep -qi "vulnerability\|security\|cve-" run_logs.txt; then + TYPE="security_failure" + FIXABLE="true" + fi + fi + + echo "type=$TYPE" >> $GITHUB_OUTPUT + echo "details<> $GITHUB_OUTPUT + echo "$DETAILS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "fixable=$FIXABLE" >> $GITHUB_OUTPUT + + echo "Diagnosed: $TYPE (fixable=$FIXABLE)" + + - name: Determine Fix Strategy + id: strategy + run: | + APPROACH="manual" + + case "${{ steps.analyze.outputs.type }}" in + lint_failure) + APPROACH="auto_lint_fix" + ;; + dependency_failure) + APPROACH="reinstall_deps" + ;; + security_failure) + APPROACH="security_patch" + ;; + test_failure) + APPROACH="ai_assisted_fix" + ;; + build_failure) + APPROACH="ai_assisted_fix" + ;; + esac + + echo "approach=$APPROACH" >> $GITHUB_OUTPUT + + # ============================================ + # Auto-Fix: Lint Issues + # ============================================ + fix-lint: + name: "Fix Lint Issues" + needs: diagnose + if: needs.diagnose.outputs.fix_strategy == 'auto_lint_fix' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Dependencies + run: npm ci --ignore-scripts 2>/dev/null || npm install --ignore-scripts + + - name: Run Lint Fix + run: | + # Try multiple linting tools + npm run lint:fix 2>/dev/null || \ + npx eslint . --fix 2>/dev/null || \ + npx prettier --write . 2>/dev/null || \ + echo "No lint fix available" + + - name: Commit Fixes + run: | + if [ -n "$(git status --porcelain)" ]; then + git config user.name "BlackRoad Self-Healer" + git config user.email "healer@blackroad.ai" + git add -A + git commit -m "fix(lint): Auto-fix linting issues + + Automated lint fixes applied by BlackRoad Self-Healing Agent. + + Co-Authored-By: BlackRoad Bot " + git push + echo "Lint fixes committed successfully" + else + echo "No lint issues to fix" + fi + + # ============================================ + # Auto-Fix: Dependencies + # ============================================ + fix-deps: + name: "Fix Dependencies" + needs: diagnose + if: needs.diagnose.outputs.fix_strategy == 'reinstall_deps' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - name: Fix Node Dependencies + if: hashFiles('package.json') != '' + run: | + # Remove node_modules and lock file, reinstall + rm -rf node_modules package-lock.json 2>/dev/null || true + npm install + + # Dedupe and fix + npm dedupe 2>/dev/null || true + npm audit fix 2>/dev/null || true + + - name: Fix Python Dependencies + if: hashFiles('requirements.txt') != '' || hashFiles('pyproject.toml') != '' + run: | + pip install --upgrade pip + [ -f "requirements.txt" ] && pip install -r requirements.txt + [ -f "pyproject.toml" ] && pip install -e . + + - name: Commit Fixes + run: | + if [ -n "$(git status --porcelain)" ]; then + git config user.name "BlackRoad Self-Healer" + git config user.email "healer@blackroad.ai" + git add -A + git commit -m "fix(deps): Reinstall and fix dependencies + + Dependency issues resolved by BlackRoad Self-Healing Agent. + + Co-Authored-By: BlackRoad Bot " + git push + fi + + # ============================================ + # Auto-Fix: Security Issues + # ============================================ + fix-security: + name: "Fix Security Issues" + needs: diagnose + if: needs.diagnose.outputs.fix_strategy == 'security_patch' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - name: Fix npm Security Issues + if: hashFiles('package.json') != '' + run: | + npm audit fix 2>/dev/null || true + npm audit fix --force 2>/dev/null || true + + - name: Fix Python Security Issues + if: hashFiles('requirements.txt') != '' + run: | + pip install safety pip-audit 2>/dev/null || true + pip-audit --fix 2>/dev/null || true + + - name: Create Security PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ -n "$(git status --porcelain)" ]; then + BRANCH="security-fix-$(date +%Y%m%d-%H%M%S)" + git config user.name "BlackRoad Self-Healer" + git config user.email "healer@blackroad.ai" + + git checkout -b "$BRANCH" + git add -A + git commit -m "fix(security): Auto-patch security vulnerabilities + + Security vulnerabilities patched by BlackRoad Self-Healing Agent. + + Co-Authored-By: BlackRoad Bot " + git push -u origin "$BRANCH" + + gh pr create \ + --title "fix(security): Auto-patch security vulnerabilities" \ + --body "## Security Patch + + This PR was automatically generated by the BlackRoad Self-Healing Agent. + + ### Changes + - Applied security patches via npm audit fix / pip-audit + + ### Verification + - Automated tests will verify compatibility + - Please review before merging + + --- + *Generated by BlackRoad Autonomous Agent*" \ + --label "security,automated" + fi + + # ============================================ + # AI-Assisted Fix + # ============================================ + ai-fix: + name: "AI-Assisted Fix" + needs: diagnose + if: needs.diagnose.outputs.fix_strategy == 'ai_assisted_fix' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - name: Request AI Fix + id: ai + run: | + # Send failure details to AI for analysis and fix + RESPONSE=$(curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/fix" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "failure_type": "${{ needs.diagnose.outputs.failure_type }}", + "details": ${{ toJSON(needs.diagnose.outputs.failure_details) }}, + "run_id": "${{ github.event.workflow_run.id }}" + }' 2>/dev/null || echo '{"status":"queued"}') + + echo "AI Response: $RESPONSE" + echo "response=$RESPONSE" >> $GITHUB_OUTPUT + + - name: Create Issue for Manual Review + if: needs.diagnose.outputs.fixable == 'maybe' + uses: actions/github-script@v7 + with: + script: | + const failureType = '${{ needs.diagnose.outputs.failure_type }}'; + const details = `${{ needs.diagnose.outputs.failure_details }}`; + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Self-Healer] ${failureType.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} Needs Review`, + body: `## Automated Failure Analysis + + **Failure Type:** ${failureType} + **Run ID:** ${{ github.event.workflow_run.id || 'N/A' }} + + ### Error Details + \`\`\` + ${details.substring(0, 2000)} + \`\`\` + + ### AI Analysis + The self-healing agent attempted to analyze this issue but requires human review. + + ### Suggested Actions + 1. Review the error logs above + 2. Check recent changes that may have caused this + 3. Apply appropriate fix + + --- + *Created by BlackRoad Self-Healing Agent*`, + labels: ['bug', 'automated', 'needs-triage'] + }); + + # ============================================ + # Report Results + # ============================================ + report: + name: "Report Results" + needs: [diagnose, fix-lint, fix-deps, fix-security, ai-fix] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Summarize Healing Attempt + run: | + echo "## Self-Healing Summary" + echo "Failure Type: ${{ needs.diagnose.outputs.failure_type }}" + echo "Fix Strategy: ${{ needs.diagnose.outputs.fix_strategy }}" + echo "Fixable: ${{ needs.diagnose.outputs.fixable }}" + + # Log to memory + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/memory" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "event": "self_heal_attempt", + "failure_type": "${{ needs.diagnose.outputs.failure_type }}", + "strategy": "${{ needs.diagnose.outputs.fix_strategy }}", + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || true