sync-squad-labels.yml (6945B)
1 name: Sync Squad Labels 2 3 on: 4 push: 5 paths: 6 - '.squad/team.md' 7 - '.ai-team/team.md' 8 workflow_dispatch: 9 10 permissions: 11 issues: write 12 contents: read 13 14 jobs: 15 sync-labels: 16 runs-on: ubuntu-latest 17 steps: 18 - uses: actions/checkout@v4 19 20 - name: Parse roster and sync labels 21 uses: actions/github-script@v7 22 with: 23 script: | 24 const fs = require('fs'); 25 let teamFile = '.squad/team.md'; 26 if (!fs.existsSync(teamFile)) { 27 teamFile = '.ai-team/team.md'; 28 } 29 30 if (!fs.existsSync(teamFile)) { 31 core.info('No .squad/team.md or .ai-team/team.md found — skipping label sync'); 32 return; 33 } 34 35 const content = fs.readFileSync(teamFile, 'utf8'); 36 const lines = content.split('\n'); 37 38 // Parse the Members table for agent names 39 const members = []; 40 let inMembersTable = false; 41 for (const line of lines) { 42 if (line.match(/^##\s+(Members|Team Roster)/i)) { 43 inMembersTable = true; 44 continue; 45 } 46 if (inMembersTable && line.startsWith('## ')) { 47 break; 48 } 49 if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { 50 const cells = line.split('|').map(c => c.trim()).filter(Boolean); 51 if (cells.length >= 2 && cells[0] !== 'Scribe') { 52 members.push({ 53 name: cells[0], 54 role: cells[1] 55 }); 56 } 57 } 58 } 59 60 core.info(`Found ${members.length} squad members: ${members.map(m => m.name).join(', ')}`); 61 62 // Check if @copilot is on the team 63 const hasCopilot = content.includes('🤖 Coding Agent'); 64 65 // Define label color palette for squad labels 66 const SQUAD_COLOR = '9B8FCC'; 67 const MEMBER_COLOR = '9B8FCC'; 68 const COPILOT_COLOR = '10b981'; 69 70 // Define go: and release: labels (static) 71 const GO_LABELS = [ 72 { name: 'go:yes', color: '0E8A16', description: 'Ready to implement' }, 73 { name: 'go:no', color: 'B60205', description: 'Not pursuing' }, 74 { name: 'go:needs-research', color: 'FBCA04', description: 'Needs investigation' } 75 ]; 76 77 const RELEASE_LABELS = [ 78 { name: 'release:v0.4.0', color: '6B8EB5', description: 'Targeted for v0.4.0' }, 79 { name: 'release:v0.5.0', color: '6B8EB5', description: 'Targeted for v0.5.0' }, 80 { name: 'release:v0.6.0', color: '8B7DB5', description: 'Targeted for v0.6.0' }, 81 { name: 'release:v1.0.0', color: '8B7DB5', description: 'Targeted for v1.0.0' }, 82 { name: 'release:backlog', color: 'D4E5F7', description: 'Not yet targeted' } 83 ]; 84 85 const TYPE_LABELS = [ 86 { name: 'type:feature', color: 'DDD1F2', description: 'New capability' }, 87 { name: 'type:bug', color: 'FF0422', description: 'Something broken' }, 88 { name: 'type:spike', color: 'F2DDD4', description: 'Research/investigation — produces a plan, not code' }, 89 { name: 'type:docs', color: 'D4E5F7', description: 'Documentation work' }, 90 { name: 'type:chore', color: 'D4E5F7', description: 'Maintenance, refactoring, cleanup' }, 91 { name: 'type:epic', color: 'CC4455', description: 'Parent issue that decomposes into sub-issues' } 92 ]; 93 94 // High-signal labels — these MUST visually dominate all others 95 const SIGNAL_LABELS = [ 96 { name: 'bug', color: 'FF0422', description: 'Something isn\'t working' }, 97 { name: 'feedback', color: '00E5FF', description: 'User feedback — high signal, needs attention' } 98 ]; 99 100 const PRIORITY_LABELS = [ 101 { name: 'priority:p0', color: 'B60205', description: 'Blocking release' }, 102 { name: 'priority:p1', color: 'D93F0B', description: 'This sprint' }, 103 { name: 'priority:p2', color: 'FBCA04', description: 'Next sprint' } 104 ]; 105 106 // Ensure the base "squad" triage label exists 107 const labels = [ 108 { name: 'squad', color: SQUAD_COLOR, description: 'Squad triage inbox — Lead will assign to a member' } 109 ]; 110 111 for (const member of members) { 112 labels.push({ 113 name: `squad:${member.name.toLowerCase()}`, 114 color: MEMBER_COLOR, 115 description: `Assigned to ${member.name} (${member.role})` 116 }); 117 } 118 119 // Add @copilot label if coding agent is on the team 120 if (hasCopilot) { 121 labels.push({ 122 name: 'squad:copilot', 123 color: COPILOT_COLOR, 124 description: 'Assigned to @copilot (Coding Agent) for autonomous work' 125 }); 126 } 127 128 // Add go:, release:, type:, priority:, and high-signal labels 129 labels.push(...GO_LABELS); 130 labels.push(...RELEASE_LABELS); 131 labels.push(...TYPE_LABELS); 132 labels.push(...PRIORITY_LABELS); 133 labels.push(...SIGNAL_LABELS); 134 135 // Sync labels (create or update) 136 for (const label of labels) { 137 try { 138 await github.rest.issues.getLabel({ 139 owner: context.repo.owner, 140 repo: context.repo.repo, 141 name: label.name 142 }); 143 // Label exists — update it 144 await github.rest.issues.updateLabel({ 145 owner: context.repo.owner, 146 repo: context.repo.repo, 147 name: label.name, 148 color: label.color, 149 description: label.description 150 }); 151 core.info(`Updated label: ${label.name}`); 152 } catch (err) { 153 if (err.status === 404) { 154 // Label doesn't exist — create it 155 await github.rest.issues.createLabel({ 156 owner: context.repo.owner, 157 repo: context.repo.repo, 158 name: label.name, 159 color: label.color, 160 description: label.description 161 }); 162 core.info(`Created label: ${label.name}`); 163 } else { 164 throw err; 165 } 166 } 167 } 168 169 core.info(`Label sync complete: ${labels.length} labels synced`);