ha-inlite

Home Assistant integration for in-lite
git clone https://git.stephank.nl/ha-inlite
Log | Files | Refs | README | LICENSE | ZIP

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`);