ha-inlite

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

squad-issue-assign.yml (6585B)


      1 name: Squad Issue Assign
      2 
      3 on:
      4   issues:
      5     types: [labeled]
      6 
      7 permissions:
      8   issues: write
      9   contents: read
     10 
     11 jobs:
     12   assign-work:
     13     # Only trigger on squad:{member} labels (not the base "squad" label)
     14     if: startsWith(github.event.label.name, 'squad:')
     15     runs-on: ubuntu-latest
     16     steps:
     17       - uses: actions/checkout@v4
     18 
     19       - name: Identify assigned member and trigger work
     20         uses: actions/github-script@v7
     21         with:
     22           script: |
     23             const fs = require('fs');
     24             const issue = context.payload.issue;
     25             const label = context.payload.label.name;
     26 
     27             // Extract member name from label (e.g., "squad:ripley" → "ripley")
     28             const memberName = label.replace('squad:', '').toLowerCase();
     29 
     30             // Read team roster — check .squad/ first, fall back to .ai-team/
     31             let teamFile = '.squad/team.md';
     32             if (!fs.existsSync(teamFile)) {
     33               teamFile = '.ai-team/team.md';
     34             }
     35             if (!fs.existsSync(teamFile)) {
     36               core.warning('No .squad/team.md or .ai-team/team.md found — cannot assign work');
     37               return;
     38             }
     39 
     40             const content = fs.readFileSync(teamFile, 'utf8');
     41             const lines = content.split('\n');
     42 
     43             // Check if this is a coding agent assignment
     44             const isCopilotAssignment = memberName === 'copilot';
     45 
     46             let assignedMember = null;
     47             if (isCopilotAssignment) {
     48               assignedMember = { name: '@copilot', role: 'Coding Agent' };
     49             } else {
     50               let inMembersTable = false;
     51               for (const line of lines) {
     52                 if (line.match(/^##\s+(Members|Team Roster)/i)) {
     53                   inMembersTable = true;
     54                   continue;
     55                 }
     56                 if (inMembersTable && line.startsWith('## ')) {
     57                   break;
     58                 }
     59                 if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) {
     60                   const cells = line.split('|').map(c => c.trim()).filter(Boolean);
     61                   if (cells.length >= 2 && cells[0].toLowerCase() === memberName) {
     62                     assignedMember = { name: cells[0], role: cells[1] };
     63                     break;
     64                   }
     65                 }
     66               }
     67             }
     68 
     69             if (!assignedMember) {
     70               core.warning(`No member found matching label "${label}"`);
     71               await github.rest.issues.createComment({
     72                 owner: context.repo.owner,
     73                 repo: context.repo.repo,
     74                 issue_number: issue.number,
     75                 body: `⚠️ No squad member found matching label \`${label}\`. Check \`.squad/team.md\` (or \`.ai-team/team.md\`) for valid member names.`
     76               });
     77               return;
     78             }
     79 
     80             // Post assignment acknowledgment
     81             let comment;
     82             if (isCopilotAssignment) {
     83               comment = [
     84                 `### 🤖 Routed to @copilot (Coding Agent)`,
     85                 '',
     86                 `**Issue:** #${issue.number} — ${issue.title}`,
     87                 '',
     88                 `@copilot has been assigned and will pick this up automatically.`,
     89                 '',
     90                 `> The coding agent will create a \`copilot/*\` branch and open a draft PR.`,
     91                 `> Review the PR as you would any team member's work.`,
     92               ].join('\n');
     93             } else {
     94               comment = [
     95                 `### 📋 Assigned to ${assignedMember.name} (${assignedMember.role})`,
     96                 '',
     97                 `**Issue:** #${issue.number} — ${issue.title}`,
     98                 '',
     99                 `${assignedMember.name} will pick this up in the next Copilot session.`,
    100                 '',
    101                 `> **For Copilot coding agent:** If enabled, this issue will be worked automatically.`,
    102                 `> Otherwise, start a Copilot session and say:`,
    103                 `> \`${assignedMember.name}, work on issue #${issue.number}\``,
    104               ].join('\n');
    105             }
    106 
    107             await github.rest.issues.createComment({
    108               owner: context.repo.owner,
    109               repo: context.repo.repo,
    110               issue_number: issue.number,
    111               body: comment
    112             });
    113 
    114             core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`);
    115 
    116       # Separate step: assign @copilot using PAT (required for coding agent)
    117       - name: Assign @copilot coding agent
    118         if: github.event.label.name == 'squad:copilot'
    119         uses: actions/github-script@v7
    120         with:
    121           github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }}
    122           script: |
    123             const owner = context.repo.owner;
    124             const repo = context.repo.repo;
    125             const issue_number = context.payload.issue.number;
    126 
    127             // Get the default branch name (main, master, etc.)
    128             const { data: repoData } = await github.rest.repos.get({ owner, repo });
    129             const baseBranch = repoData.default_branch;
    130 
    131             try {
    132               await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', {
    133                 owner,
    134                 repo,
    135                 issue_number,
    136                 assignees: ['copilot-swe-agent[bot]'],
    137                 agent_assignment: {
    138                   target_repo: `${owner}/${repo}`,
    139                   base_branch: baseBranch,
    140                   custom_instructions: '',
    141                   custom_agent: '',
    142                   model: ''
    143                 },
    144                 headers: {
    145                   'X-GitHub-Api-Version': '2022-11-28'
    146                 }
    147               });
    148               core.info(`Assigned copilot-swe-agent to issue #${issue_number} (base: ${baseBranch})`);
    149             } catch (err) {
    150               core.warning(`Assignment with agent_assignment failed: ${err.message}`);
    151               // Fallback: try without agent_assignment
    152               try {
    153                 await github.rest.issues.addAssignees({
    154                   owner, repo, issue_number,
    155                   assignees: ['copilot-swe-agent']
    156                 });
    157                 core.info(`Fallback assigned copilot-swe-agent to issue #${issue_number}`);
    158               } catch (err2) {
    159                 core.warning(`Fallback also failed: ${err2.message}`);
    160               }
    161             }