|
| 1 | +# .github/workflows/agent-workflow-sync.yml |
| 2 | +# Self-improving agent that keeps all org RoadCode repos in sync |
| 3 | +# When workflows change in operator, automatically propagates to all 16 orgs |
| 4 | +# Also detects and disables broken workflows across all orgs |
| 5 | + |
| 6 | +name: "Agent: Workflow Sync" |
| 7 | + |
| 8 | +on: |
| 9 | + push: |
| 10 | + branches: [main] |
| 11 | + paths: |
| 12 | + - '.github/workflows/autonomous-*.yml' |
| 13 | + - '.github/workflows/agent-*.yml' |
| 14 | + schedule: |
| 15 | + - cron: '0 12 * * 1' # Weekly Monday noon UTC |
| 16 | + workflow_dispatch: |
| 17 | + inputs: |
| 18 | + action: |
| 19 | + description: 'Action' |
| 20 | + required: false |
| 21 | + default: 'sync' |
| 22 | + type: choice |
| 23 | + options: |
| 24 | + - sync |
| 25 | + - disable_broken |
| 26 | + - full |
| 27 | + |
| 28 | +permissions: |
| 29 | + contents: write |
| 30 | + |
| 31 | +jobs: |
| 32 | + sync-workflows: |
| 33 | + name: "Sync Workflows to All Orgs" |
| 34 | + runs-on: ubuntu-latest |
| 35 | + if: github.event_name == 'push' || inputs.action == 'sync' || inputs.action == 'full' |
| 36 | + |
| 37 | + steps: |
| 38 | + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 |
| 39 | + |
| 40 | + - name: Sync autonomous workflows to all RoadCode repos |
| 41 | + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 |
| 42 | + with: |
| 43 | + script: | |
| 44 | + const ORGS = [ |
| 45 | + 'BlackRoad-OS-Inc', 'BlackRoad-OS', 'BlackRoad-Studio', 'BlackRoad-Archive', |
| 46 | + 'BlackRoad-Interactive', 'BlackRoad-Security', 'BlackRoad-Gov', 'BlackRoad-Education', |
| 47 | + 'BlackRoad-Hardware', 'BlackRoad-Media', 'BlackRoad-Foundation', 'BlackRoad-Ventures', |
| 48 | + 'BlackRoad-Cloud', 'BlackRoad-Labs', 'BlackRoad-AI', 'Blackbox-Enterprises' |
| 49 | + ]; |
| 50 | +
|
| 51 | + const WORKFLOWS = [ |
| 52 | + 'autonomous-orchestrator.yml', |
| 53 | + 'autonomous-self-healer.yml', |
| 54 | + 'autonomous-issue-manager.yml', |
| 55 | + 'autonomous-cross-repo.yml', |
| 56 | + 'autonomous-dependency-manager.yml' |
| 57 | + ]; |
| 58 | +
|
| 59 | + let synced = 0, errors = 0; |
| 60 | +
|
| 61 | + for (const wf of WORKFLOWS) { |
| 62 | + // Read source workflow from this repo |
| 63 | + let sourceContent; |
| 64 | + try { |
| 65 | + const { data } = await github.rest.repos.getContent({ |
| 66 | + owner: 'BlackRoad-OS-Inc', repo: 'blackroad-operator', |
| 67 | + path: `.github/workflows/${wf}` |
| 68 | + }); |
| 69 | + sourceContent = data.content; |
| 70 | + } catch (e) { |
| 71 | + console.log(`Source not found: ${wf}`); |
| 72 | + continue; |
| 73 | + } |
| 74 | +
|
| 75 | + for (const org of ORGS) { |
| 76 | + try { |
| 77 | + // Check if target exists |
| 78 | + let sha; |
| 79 | + try { |
| 80 | + const { data } = await github.rest.repos.getContent({ |
| 81 | + owner: org, repo: 'RoadCode', |
| 82 | + path: `.github/workflows/${wf}` |
| 83 | + }); |
| 84 | + sha = data.sha; |
| 85 | +
|
| 86 | + // Skip if content is identical |
| 87 | + if (data.content.replace(/\s/g, '') === sourceContent.replace(/\s/g, '')) { |
| 88 | + continue; |
| 89 | + } |
| 90 | + } catch {} |
| 91 | +
|
| 92 | + const params = { |
| 93 | + owner: org, repo: 'RoadCode', |
| 94 | + path: `.github/workflows/${wf}`, |
| 95 | + message: `agent: sync ${wf} from operator`, |
| 96 | + content: sourceContent, |
| 97 | + committer: { name: 'BlackRoad Agent', email: 'agent@blackroad.io' } |
| 98 | + }; |
| 99 | + if (sha) params.sha = sha; |
| 100 | +
|
| 101 | + await github.rest.repos.createOrUpdateFileContents(params); |
| 102 | + synced++; |
| 103 | + console.log(`Synced: ${org}/RoadCode/${wf}`); |
| 104 | + } catch (e) { |
| 105 | + errors++; |
| 106 | + console.log(`Error: ${org}/RoadCode/${wf} — ${e.message}`); |
| 107 | + } |
| 108 | + } |
| 109 | + } |
| 110 | +
|
| 111 | + console.log(`\nSynced: ${synced} | Errors: ${errors}`); |
| 112 | + await core.summary.addRaw(`## Workflow Sync\n\nSynced: ${synced} workflows\nErrors: ${errors}`).write(); |
| 113 | +
|
| 114 | + disable-broken: |
| 115 | + name: "Disable Broken Workflows" |
| 116 | + runs-on: ubuntu-latest |
| 117 | + if: github.event_name == 'schedule' || inputs.action == 'disable_broken' || inputs.action == 'full' |
| 118 | + |
| 119 | + steps: |
| 120 | + - name: Find and disable failing scheduled workflows |
| 121 | + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 |
| 122 | + with: |
| 123 | + script: | |
| 124 | + const ORGS = [ |
| 125 | + 'BlackRoad-OS-Inc', 'BlackRoad-OS', 'BlackRoad-AI', 'BlackRoad-Studio', |
| 126 | + 'BlackRoad-Education', 'BlackRoad-Security', 'BlackRoad-Labs', 'BlackRoad-Hardware', |
| 127 | + 'BlackRoad-Media', 'BlackRoad-Foundation', 'BlackRoad-Ventures', 'BlackRoad-Cloud', |
| 128 | + 'BlackRoad-Gov', 'BlackRoad-Archive', 'BlackRoad-Interactive', 'Blackbox-Enterprises' |
| 129 | + ]; |
| 130 | +
|
| 131 | + // Patterns that indicate broken looping workflows |
| 132 | + const DISABLE_PATTERNS = [ |
| 133 | + 'Self-Healing', 'self-heal', 'Auto-Heal', 'auto-heal', |
| 134 | + 'HuggingFace', 'Autonomy System', 'BlackRoad AI Agents', |
| 135 | + 'Production Health Check', 'Uptime' |
| 136 | + ]; |
| 137 | +
|
| 138 | + let disabled = 0, checked = 0; |
| 139 | +
|
| 140 | + for (const org of ORGS) { |
| 141 | + const { data: repos } = await github.rest.repos.listForOrg({ org, per_page: 100 }); |
| 142 | +
|
| 143 | + for (const repo of repos.filter(r => !r.archived)) { |
| 144 | + try { |
| 145 | + const { data: workflows } = await github.rest.actions.listRepoWorkflows({ |
| 146 | + owner: org, repo: repo.name |
| 147 | + }); |
| 148 | +
|
| 149 | + for (const wf of workflows.workflows) { |
| 150 | + if (wf.state !== 'active') continue; |
| 151 | + checked++; |
| 152 | +
|
| 153 | + // Check if it matches a broken pattern |
| 154 | + const shouldDisable = DISABLE_PATTERNS.some(p => |
| 155 | + wf.name.includes(p) || wf.path.includes(p.toLowerCase().replace(/ /g, '-')) |
| 156 | + ); |
| 157 | +
|
| 158 | + if (!shouldDisable) continue; |
| 159 | +
|
| 160 | + // Verify it's actually failing (last 3 runs all failed) |
| 161 | + try { |
| 162 | + const { data: runs } = await github.rest.actions.listWorkflowRuns({ |
| 163 | + owner: org, repo: repo.name, workflow_id: wf.id, |
| 164 | + per_page: 3, status: 'completed' |
| 165 | + }); |
| 166 | +
|
| 167 | + const allFailed = runs.workflow_runs.length >= 3 && |
| 168 | + runs.workflow_runs.every(r => r.conclusion === 'failure'); |
| 169 | +
|
| 170 | + if (allFailed) { |
| 171 | + await github.rest.actions.disableWorkflow({ |
| 172 | + owner: org, repo: repo.name, workflow_id: wf.id |
| 173 | + }); |
| 174 | + disabled++; |
| 175 | + console.log(`Disabled: ${org}/${repo.name}/${wf.name} (3+ consecutive failures)`); |
| 176 | + } |
| 177 | + } catch {} |
| 178 | + } |
| 179 | + } catch {} |
| 180 | + } |
| 181 | + } |
| 182 | +
|
| 183 | + console.log(`\nChecked: ${checked} workflows | Disabled: ${disabled}`); |
| 184 | + await core.summary.addRaw(`## Broken Workflow Cleanup\n\nChecked: ${checked}\nDisabled: ${disabled} (3+ consecutive failures)`).write(); |
0 commit comments