From 0890e3a1b17539dce7341b516ce4eeae22e4da61 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 20:00:23 -0500 Subject: [PATCH 01/33] feat: add @copilot coding agent as squad member type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New member type: šŸ¤– Coding Agent with three-tier capability profile - Init Mode asks if user wants @copilot on the team - Configurable auto-assign via in team.md - Lead evaluates issues against capability profile during triage - copilot-instructions.md template for autonomous coding agent context - Upgrade updates copilot-instructions.md when @copilot is enabled - squad:copilot label synced automatically - Workflows: triage routes to @copilot, issue-assign auto-assigns - Intercept rule ensures 'Enable @copilot' is not treated as greeting - Docs: feature guide, README, upgrading docs updated - Tests: 5 new tests (38 total, all passing) --- .github/agents/squad.agent.md | 130 ++++++++++- .github/copilot-instructions.md | 46 ++++ .github/workflows/squad-issue-assign.yml | 133 +++++++++++ .github/workflows/squad-triage.yml | 246 +++++++++++++++++++++ .github/workflows/sync-squad-labels.yml | 122 ++++++++++ README.md | 2 + docs/features/copilot-coding-agent.md | 148 +++++++++++++ docs/scenarios/upgrading.md | 2 + index.js | 48 ++++ templates/copilot-instructions.md | 46 ++++ templates/roster.md | 33 +++ templates/routing.md | 25 ++- templates/workflows/squad-issue-assign.yml | 91 ++++++-- templates/workflows/squad-triage.yml | 157 +++++++++---- templates/workflows/sync-squad-labels.yml | 13 ++ test/index.test.js | 65 ++++++ 16 files changed, 1237 insertions(+), 70 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/squad-issue-assign.yml create mode 100644 .github/workflows/squad-triage.yml create mode 100644 .github/workflows/sync-squad-labels.yml create mode 100644 docs/features/copilot-coding-agent.md create mode 100644 templates/copilot-instructions.md diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 75efb5449..7c58d060c 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -98,10 +98,15 @@ The `union` merge driver keeps all lines from both sides, which is correct for a → If yes, follow the GitHub Issues Mode flow to connect and list the backlog. - *"Are any humans joining the team? (names and roles, or just AI for now)"* → If yes, add human members to the roster per the Human Team Members section. + - *"Want to include the Copilot coding agent (@copilot)? It can pick up issues autonomously — bug fixes, tests, small features. (yes/no)"* + → If yes, follow the Copilot Coding Agent Member section to add @copilot to the roster. + → Also ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/always for good-fit issues/no)"* + → Generate the default capability profile (good fit / needs review / not suitable) and let the user customize. - These are additive. The user can answer all, some, or skip entirely. Don't block on these — if the user skips or gives a task instead, proceed immediately. - **PRD provided?** → Run the PRD Mode intake flow: spawn Lead to decompose, present work items. - **GitHub repo provided?** → Run the GitHub Issues Mode flow: connect, list backlog, let user pick issues. - **Humans added?** → Already in roster. Confirm: *"šŸ‘¤ {Name} is on the team as {Role}. I'll tag them when their input is needed."* + - **@copilot enabled?** → Already in roster with capability profile. Confirm: *"šŸ¤– @copilot is on the team. It'll pick up issues that match its capability profile."* --- @@ -109,6 +114,8 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **āš ļø CRITICAL RULE: Every agent interaction MUST use the `task` tool to spawn a real agent. You MUST call the `task` tool — never simulate, role-play, or inline an agent's work. If you did not call the `task` tool, the agent was NOT spawned. No exceptions.** +**āš ļø INTERCEPT RULE — @copilot commands:** If the user's message contains "enable @copilot", "add @copilot", "add coding agent", "disable @copilot", "remove coding agent", or "update @copilot capabilities" — this is a **team management command**, NOT a session greeting, NOT a file reference, NOT a status check. Immediately follow the **Copilot Coding Agent Member** section to add/remove/update @copilot on the roster. Do not interpret "@copilot" as a file path or project reference. + **On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.ai-team/` paths must be resolved relative to it. Pass the team root into every spawn prompt as `TEAM_ROOT` and the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. **⚔ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -195,13 +202,15 @@ The routing table determines **WHO** handles work. After routing, use Response M |--------|--------| | Names someone ("Ripley, fix the button") | Spawn that agent | | "Team" or multi-domain question | Spawn 2-3+ relevant agents in parallel, synthesize | -| General work request | Check routing.md, spawn best match + any anticipatory agents | -| Quick factual question | Answer directly (no spawn) | -| Ambiguous | Pick the most likely agent; say who you chose | +| Coding agent management ("enable @copilot", "add coding agent", "disable @copilot", "update @copilot capabilities") | Follow Copilot Coding Agent Member (see that section) — **do not treat as a status check** | +| Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | +| Issue suitable for @copilot ("this looks like a @copilot task", "@copilot could handle this") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | | Ceremony request ("design meeting", "run a retro") | Run the matching ceremony from `ceremonies.md` (see Ceremonies) | | Issues/backlog request ("pull issues", "show backlog", "work on #N") | Follow GitHub Issues Mode (see that section) | | PRD intake ("here's the PRD", "read the PRD at X", pastes spec) | Follow PRD Mode (see that section) | -| Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | +| General work request | Check routing.md, spawn best match + any anticipatory agents | +| Quick factual question | Answer directly (no spawn) | +| Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | **Skill-aware routing:** Before spawning, check `.ai-team/skills/` for skills relevant to the task domain. If a matching skill exists, add to the spawn prompt: `Relevant skill: .ai-team/skills/{name}/SKILL.md — read before starting.` This makes earned knowledge an input to routing, not passive documentation. @@ -1482,4 +1491,117 @@ Example roster with mixed team: | Dallas | Lead | .ai-team/agents/dallas/charter.md | āœ… Active | | Brady | PM | — | šŸ‘¤ Human | | Sarah | Designer | — | šŸ‘¤ Human | +| @copilot | Coding Agent | — | šŸ¤– Coding Agent | +``` + +## Copilot Coding Agent Member + +The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous team member. Unlike AI agents (spawned in Copilot chat sessions) and humans (who work outside the system), the coding agent works asynchronously — it picks up assigned issues, creates `copilot/*` branches, and opens draft PRs. + +### Triggers + +| User says | Action | +|-----------|--------| +| "enable @copilot" / "add coding agent" / "add @copilot to the team" | Add @copilot to roster with capability profile | +| "disable @copilot" / "remove coding agent" | Remove @copilot from roster | +| "update @copilot capabilities" / "change what @copilot can do" | Edit the capability profile in team.md | +| "auto-assign issues to @copilot" / "turn on @copilot auto-assign" | Set `` in team.md | +| "stop auto-assigning to @copilot" | Set `` in team.md | +| "@copilot can handle this" / "route to coding agent" | Route current issue to @copilot | + +### How the Coding Agent Differs + +| Aspect | AI Agent | Human Member | Coding Agent (@copilot) | +|--------|----------|-------------|------------------------| +| **Badge** | āœ… Active | šŸ‘¤ Human | šŸ¤– Coding Agent | +| **Casting** | Named from universe | Real name | Always "@copilot" | +| **Charter** | Full charter.md | No charter | No charter — uses `copilot-instructions.md` | +| **Spawnable** | Yes (via `task` tool) | No — coordinator pauses | No — works via issue assignment | +| **History** | Writes to history.md | No history file | No history file | +| **Routing** | Auto-routed by coordinator | Coordinator presents, waits | Routed via issue labels + GitHub assignment | +| **Work style** | Synchronous in session | Asynchronous (human pace) | Asynchronous (creates branch + PR) | +| **Scope** | Full domain per charter | Role-based | Capability profile (three tiers) | + +### Adding @copilot to the Team + +1. Add to `.ai-team/team.md` roster under the **Coding Agent** section: + +```markdown + + +| Name | Role | Charter | Status | +|------|------|---------|--------| +| @copilot | Coding Agent | — | šŸ¤– Coding Agent | + +### Capabilities + +🟢 Good fit: Bug fixes, test coverage, lint fixes, dependency updates, small features, scaffolding, doc fixes +🟔 Needs review: Medium features with clear specs, refactoring with tests, API additions +šŸ”“ Not suitable: Architecture decisions, multi-system design, ambiguous requirements, security-critical changes +``` + +2. Add routing entries to `.ai-team/routing.md`: + +```markdown +| Bug fixes, test coverage, lint fixes | @copilot šŸ¤– | Small, well-defined tasks with clear acceptance criteria | ``` + +3. Ensure `.github/copilot-instructions.md` exists (created during `squad init` if @copilot is enabled, or copy from `.ai-team-templates/copilot-instructions.md`). + +4. Announce: `"šŸ¤– @copilot joined the team as Coding Agent. I'll route suitable issues to it based on the capability profile."` + +### Capability Profile + +The capability profile lives in `team.md` under the @copilot entry. It defines three tiers: + +- **🟢 Good fit** — The coding agent can handle these autonomously. If auto-assign is enabled, these issues get assigned to `@copilot` automatically. +- **🟔 Needs review** — The coding agent can do the work, but a squad member should review the PR before merging. The triage comment and PR description flag this. +- **šŸ”“ Not suitable** — These should go to a squad member. If @copilot is accidentally assigned one, it should comment on the issue requesting reassignment. + +The profile is a living document. The Lead can suggest updates based on what @copilot handles well or poorly: +- *"@copilot nailed that refactoring — I'm bumping refactoring to 🟢 good fit."* +- *"That API change needed too much context — keeping multi-endpoint work at šŸ”“."* + +### Auto-Assign Behavior + +When `` is set in `team.md`: + +1. The `squad-issue-assign` workflow checks if the issue matches @copilot's capability profile. +2. If it's a 🟢 good fit, `@copilot` is added as the issue assignee — the coding agent picks it up automatically. +3. If it's a 🟔 needs review, `@copilot` is assigned but the comment flags that PR review is needed. +4. If it's a šŸ”“ not suitable or no match, the issue is NOT assigned to @copilot — it follows normal squad routing. + +When auto-assign is disabled, the workflow still comments with instructions but doesn't assign @copilot. Users can manually assign @copilot on any issue. + +### Lead Triage and @copilot + +During triage (in-session or via the `squad-triage` workflow), the Lead evaluates each issue against @copilot's capability profile: + +1. **Good fit?** → Suggest routing to @copilot: *"šŸ¤– This looks like a good @copilot task — it's a straightforward bug fix with clear repro steps."* +2. **Needs review?** → Route to @copilot with a flag: *"šŸ¤– Routing to @copilot, but this is a medium-complexity feature — {ReviewerName} should review the PR."* +3. **Not suitable?** → Route to squad member as normal, but note why: *"This needs architectural thinking — routing to {LeadName} instead of @copilot."* + +The Lead can also **reassign**: +- If a squad member has an issue that looks more suitable for @copilot: *"This test coverage task could go to @copilot — want me to reassign?"* +- If @copilot has an issue that's more complex than expected: *"@copilot might struggle with this — suggesting we reassign to {MemberName}."* + +### Routing to @copilot + +When work routes to @copilot, the coordinator does NOT spawn an agent. Instead: + +1. **Present the routing decision:** + ``` + šŸ¤– Routing to @copilot — {description of what's needed}. + Capability match: {🟢 Good fit / 🟔 Needs review} + + The coding agent will pick this up when the issue is assigned. + ``` + +2. **If auto-assign is enabled**, the workflow handles assignment automatically. + +3. **If auto-assign is disabled**, tell the user: + ``` + Assign @copilot on the issue to start autonomous work, or say "assign it" and I'll note it for you. + ``` + +4. **Non-dependent work continues immediately.** Like human blocks, @copilot routing does not serialize the rest of the team. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..7bfa98a32 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,46 @@ +# Copilot Coding Agent — Squad Instructions + +You are working on a project that uses **Squad**, an AI team framework. When picking up issues autonomously, follow these guidelines. + +## Team Context + +Before starting work on any issue: + +1. Read `.ai-team/team.md` for the team roster, member roles, and your capability profile. +2. Read `.ai-team/routing.md` for work routing rules. +3. If the issue has a `squad:{member}` label, read that member's charter at `.ai-team/agents/{member}/charter.md` to understand their domain expertise and coding style — work in their voice. + +## Capability Self-Check + +Before starting work, check your capability profile in `.ai-team/team.md` under the **Coding Agent → Capabilities** section. + +- **🟢 Good fit** — proceed autonomously. +- **🟔 Needs review** — proceed, but note in the PR description that a squad member should review. +- **šŸ”“ Not suitable** — do NOT start work. Instead, comment on the issue: + ``` + šŸ¤– This issue doesn't match my capability profile (reason: {why}). Suggesting reassignment to a squad member. + ``` + +## Branch Naming + +Use the squad branch convention: +``` +squad/{issue-number}-{kebab-case-slug} +``` +Example: `squad/42-fix-login-validation` + +## PR Guidelines + +When opening a PR: +- Reference the issue: `Closes #{issue-number}` +- If the issue had a `squad:{member}` label, mention the member: `Working as {member} ({role})` +- If this is a 🟔 needs-review task, add to the PR description: `āš ļø This task was flagged as "needs review" — please have a squad member review before merging.` +- Follow any project conventions in `.ai-team/decisions.md` + +## Decisions + +If you make a decision that affects other team members, write it to: +``` +.ai-team/decisions/inbox/copilot-{brief-slug}.md +``` +The Scribe will merge it into the shared decisions file. diff --git a/.github/workflows/squad-issue-assign.yml b/.github/workflows/squad-issue-assign.yml new file mode 100644 index 000000000..01632a5d8 --- /dev/null +++ b/.github/workflows/squad-issue-assign.yml @@ -0,0 +1,133 @@ +name: Squad Issue Assign + +on: + issues: + types: [labeled] + +permissions: + issues: write + contents: read + +jobs: + assign-work: + # Only trigger on squad:{member} labels (not the base "squad" label) + if: startsWith(github.event.label.name, 'squad:') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Identify assigned member and trigger work + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const issue = context.payload.issue; + const label = context.payload.label.name; + + // Extract member name from label (e.g., "squad:ripley" → "ripley") + const memberName = label.replace('squad:', '').toLowerCase(); + + // Read team roster to find the member + const teamFile = '.ai-team/team.md'; + if (!fs.existsSync(teamFile)) { + core.warning('No .ai-team/team.md found — cannot assign work'); + return; + } + + const content = fs.readFileSync(teamFile, 'utf8'); + const lines = content.split('\n'); + + // Check if @copilot auto-assign is enabled + const copilotAutoAssign = content.includes(''); + + // Check if this is a coding agent assignment + const isCopilotAssignment = memberName === 'copilot'; + + let assignedMember = null; + if (isCopilotAssignment) { + assignedMember = { name: '@copilot', role: 'Coding Agent' }; + } else { + let inMembersTable = false; + for (const line of lines) { + if (line.startsWith('## Members')) { + inMembersTable = true; + continue; + } + if (inMembersTable && line.startsWith('## ')) { + break; + } + if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { + const cells = line.split('|').map(c => c.trim()).filter(Boolean); + if (cells.length >= 2 && cells[0].toLowerCase() === memberName) { + assignedMember = { name: cells[0], role: cells[1] }; + break; + } + } + } + } + + if (!assignedMember) { + core.warning(`No member found matching label "${label}"`); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `āš ļø No squad member found matching label \`${label}\`. Check \`.ai-team/team.md\` for valid member names.` + }); + return; + } + + // Auto-assign @copilot if enabled and this is a copilot assignment + if (isCopilotAssignment && copilotAutoAssign) { + try { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + assignees: ['copilot'] + }); + core.info(`Auto-assigned @copilot to issue #${issue.number}`); + } catch (err) { + core.warning(`Could not auto-assign @copilot: ${err.message}`); + } + } + + // Post assignment acknowledgment + let comment; + if (isCopilotAssignment) { + const autoAssignNote = copilotAutoAssign + ? `@copilot has been assigned and will pick this up automatically.` + : `Assign @copilot on this issue to start autonomous work.`; + + comment = [ + `### šŸ¤– Routed to @copilot (Coding Agent)`, + '', + `**Issue:** #${issue.number} — ${issue.title}`, + '', + autoAssignNote, + '', + `> The coding agent will create a \`copilot/*\` branch and open a draft PR.`, + `> Review the PR as you would any team member's work.`, + ].join('\n'); + } else { + comment = [ + `### šŸ“‹ Assigned to ${assignedMember.name} (${assignedMember.role})`, + '', + `**Issue:** #${issue.number} — ${issue.title}`, + '', + `${assignedMember.name} will pick this up in the next Copilot session.`, + '', + `> **For Copilot coding agent:** If enabled, this issue will be worked automatically.`, + `> Otherwise, start a Copilot session and say:`, + `> \`${assignedMember.name}, work on issue #${issue.number}\``, + ].join('\n'); + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: comment + }); + + core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); diff --git a/.github/workflows/squad-triage.yml b/.github/workflows/squad-triage.yml new file mode 100644 index 000000000..fb1c3e19c --- /dev/null +++ b/.github/workflows/squad-triage.yml @@ -0,0 +1,246 @@ +name: Squad Triage + +on: + issues: + types: [labeled] + +permissions: + issues: write + contents: read + +jobs: + triage: + if: github.event.label.name == 'squad' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Triage issue via Lead agent + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const issue = context.payload.issue; + + // Read team roster to find the Lead and all members + const teamFile = '.ai-team/team.md'; + if (!fs.existsSync(teamFile)) { + core.warning('No .ai-team/team.md found — cannot triage'); + return; + } + + const content = fs.readFileSync(teamFile, 'utf8'); + const lines = content.split('\n'); + + // Check if @copilot is on the team + const hasCopilot = content.includes('šŸ¤– Coding Agent'); + const copilotAutoAssign = content.includes(''); + + // Parse @copilot capability profile + let goodFitKeywords = []; + let needsReviewKeywords = []; + let notSuitableKeywords = []; + + if (hasCopilot) { + // Extract capability tiers from team.md + const goodFitMatch = content.match(/🟢\s*Good fit[^:]*:\s*(.+)/i); + const needsReviewMatch = content.match(/🟔\s*Needs review[^:]*:\s*(.+)/i); + const notSuitableMatch = content.match(/šŸ”“\s*Not suitable[^:]*:\s*(.+)/i); + + if (goodFitMatch) { + goodFitKeywords = goodFitMatch[1].toLowerCase().split(',').map(s => s.trim()); + } else { + goodFitKeywords = ['bug fix', 'test coverage', 'lint', 'format', 'dependency update', 'small feature', 'scaffolding', 'doc fix', 'documentation']; + } + if (needsReviewMatch) { + needsReviewKeywords = needsReviewMatch[1].toLowerCase().split(',').map(s => s.trim()); + } else { + needsReviewKeywords = ['medium feature', 'refactoring', 'api endpoint', 'migration']; + } + if (notSuitableMatch) { + notSuitableKeywords = notSuitableMatch[1].toLowerCase().split(',').map(s => s.trim()); + } else { + notSuitableKeywords = ['architecture', 'system design', 'security', 'auth', 'encryption', 'performance']; + } + } + + const members = []; + let inMembersTable = false; + for (const line of lines) { + if (line.startsWith('## Members')) { + inMembersTable = true; + continue; + } + if (inMembersTable && line.startsWith('## ')) { + break; + } + if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { + const cells = line.split('|').map(c => c.trim()).filter(Boolean); + if (cells.length >= 2 && cells[0] !== 'Scribe') { + members.push({ + name: cells[0], + role: cells[1] + }); + } + } + } + + // Read routing rules + const routingFile = '.ai-team/routing.md'; + let routingContent = ''; + if (fs.existsSync(routingFile)) { + routingContent = fs.readFileSync(routingFile, 'utf8'); + } + + // Find the Lead + const lead = members.find(m => + m.role.toLowerCase().includes('lead') || + m.role.toLowerCase().includes('architect') || + m.role.toLowerCase().includes('coordinator') + ); + + if (!lead) { + core.warning('No Lead role found in team roster — cannot triage'); + return; + } + + // Build triage context + const memberList = members.map(m => + `- **${m.name}** (${m.role}) → label: \`squad:${m.name.toLowerCase()}\`` + ).join('\n'); + + // Determine best assignee based on issue content and routing + const issueText = `${issue.title}\n${issue.body || ''}`.toLowerCase(); + + let assignedMember = null; + let triageReason = ''; + let copilotTier = null; + + // First, evaluate @copilot fit if enabled + if (hasCopilot) { + const isNotSuitable = notSuitableKeywords.some(kw => issueText.includes(kw)); + const isGoodFit = !isNotSuitable && goodFitKeywords.some(kw => issueText.includes(kw)); + const isNeedsReview = !isNotSuitable && !isGoodFit && needsReviewKeywords.some(kw => issueText.includes(kw)); + + if (isGoodFit) { + copilotTier = 'good-fit'; + assignedMember = { name: '@copilot', role: 'Coding Agent' }; + triageReason = '🟢 Good fit for @copilot — matches capability profile'; + } else if (isNeedsReview) { + copilotTier = 'needs-review'; + assignedMember = { name: '@copilot', role: 'Coding Agent' }; + triageReason = '🟔 Routing to @copilot (needs review) — a squad member should review the PR'; + } else if (isNotSuitable) { + copilotTier = 'not-suitable'; + // Fall through to normal routing + } + } + + // If not routed to @copilot, use keyword-based routing + if (!assignedMember) { + for (const member of members) { + const role = member.role.toLowerCase(); + if ((role.includes('frontend') || role.includes('ui')) && + (issueText.includes('ui') || issueText.includes('frontend') || + issueText.includes('css') || issueText.includes('component') || + issueText.includes('button') || issueText.includes('page') || + issueText.includes('layout') || issueText.includes('design'))) { + assignedMember = member; + triageReason = 'Issue relates to frontend/UI work'; + break; + } + if ((role.includes('backend') || role.includes('api') || role.includes('server')) && + (issueText.includes('api') || issueText.includes('backend') || + issueText.includes('database') || issueText.includes('endpoint') || + issueText.includes('server') || issueText.includes('auth'))) { + assignedMember = member; + triageReason = 'Issue relates to backend/API work'; + break; + } + if ((role.includes('test') || role.includes('qa') || role.includes('quality')) && + (issueText.includes('test') || issueText.includes('bug') || + issueText.includes('fix') || issueText.includes('regression') || + issueText.includes('coverage'))) { + assignedMember = member; + triageReason = 'Issue relates to testing/quality work'; + break; + } + if ((role.includes('devops') || role.includes('infra') || role.includes('ops')) && + (issueText.includes('deploy') || issueText.includes('ci') || + issueText.includes('pipeline') || issueText.includes('docker') || + issueText.includes('infrastructure'))) { + assignedMember = member; + triageReason = 'Issue relates to DevOps/infrastructure work'; + break; + } + } + } + + // Default to Lead if no routing match + if (!assignedMember) { + assignedMember = lead; + triageReason = 'No specific domain match — assigned to Lead for further analysis'; + } + + const isCopilot = assignedMember.name === '@copilot'; + const assignLabel = isCopilot ? 'squad:copilot' : `squad:${assignedMember.name.toLowerCase()}`; + + // Add the member-specific label + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: [assignLabel] + }); + + // Auto-assign @copilot if enabled + if (isCopilot && copilotAutoAssign) { + try { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + assignees: ['copilot'] + }); + } catch (err) { + core.warning(`Could not auto-assign @copilot: ${err.message}`); + } + } + + // Build copilot evaluation note + let copilotNote = ''; + if (hasCopilot && !isCopilot) { + if (copilotTier === 'not-suitable') { + copilotNote = `\n\n**@copilot evaluation:** šŸ”“ Not suitable — issue involves work outside the coding agent's capability profile.`; + } else { + copilotNote = `\n\n**@copilot evaluation:** No strong capability match — routed to squad member.`; + } + } + + // Post triage comment + const comment = [ + `### šŸ—ļø Squad Triage — ${lead.name} (${lead.role})`, + '', + `**Issue:** #${issue.number} — ${issue.title}`, + `**Assigned to:** ${assignedMember.name} (${assignedMember.role})`, + `**Reason:** ${triageReason}`, + copilotTier === 'needs-review' ? `\nāš ļø **PR review recommended** — a squad member should review @copilot's work on this one.` : '', + copilotNote, + '', + `---`, + '', + `**Team roster:**`, + memberList, + hasCopilot ? `- **@copilot** (Coding Agent) → label: \`squad:copilot\`` : '', + '', + `> To reassign, remove the current \`squad:*\` label and add the correct one.`, + ].filter(Boolean).join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: comment + }); + + core.info(`Triaged issue #${issue.number} → ${assignedMember.name} (${assignLabel})`); diff --git a/.github/workflows/sync-squad-labels.yml b/.github/workflows/sync-squad-labels.yml new file mode 100644 index 000000000..67b730732 --- /dev/null +++ b/.github/workflows/sync-squad-labels.yml @@ -0,0 +1,122 @@ +name: Sync Squad Labels + +on: + push: + paths: + - '.ai-team/team.md' + workflow_dispatch: + +permissions: + issues: write + contents: read + +jobs: + sync-labels: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Parse roster and sync labels + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const teamFile = '.ai-team/team.md'; + + if (!fs.existsSync(teamFile)) { + core.info('No .ai-team/team.md found — skipping label sync'); + return; + } + + const content = fs.readFileSync(teamFile, 'utf8'); + const lines = content.split('\n'); + + // Parse the Members table for agent names + const members = []; + let inMembersTable = false; + for (const line of lines) { + if (line.startsWith('## Members')) { + inMembersTable = true; + continue; + } + if (inMembersTable && line.startsWith('## ')) { + break; + } + if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { + const cells = line.split('|').map(c => c.trim()).filter(Boolean); + if (cells.length >= 2 && cells[0] !== 'Scribe') { + members.push({ + name: cells[0], + role: cells[1] + }); + } + } + } + + core.info(`Found ${members.length} squad members: ${members.map(m => m.name).join(', ')}`); + + // Check if @copilot is on the team + const hasCopilot = content.includes('šŸ¤– Coding Agent'); + + // Define label color palette for squad labels + const SQUAD_COLOR = '6366f1'; + const MEMBER_COLOR = '3b82f6'; + const COPILOT_COLOR = '10b981'; + + // Ensure the base "squad" triage label exists + const labels = [ + { name: 'squad', color: SQUAD_COLOR, description: 'Squad triage inbox — Lead will assign to a member' } + ]; + + for (const member of members) { + labels.push({ + name: `squad:${member.name.toLowerCase()}`, + color: MEMBER_COLOR, + description: `Assigned to ${member.name} (${member.role})` + }); + } + + // Add @copilot label if coding agent is on the team + if (hasCopilot) { + labels.push({ + name: 'squad:copilot', + color: COPILOT_COLOR, + description: 'Assigned to @copilot (Coding Agent) for autonomous work' + }); + } + + // Sync labels (create or update) + for (const label of labels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name + }); + // Label exists — update it + await github.rest.issues.updateLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + color: label.color, + description: label.description + }); + core.info(`Updated label: ${label.name}`); + } catch (err) { + if (err.status === 404) { + // Label doesn't exist — create it + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + color: label.color, + description: label.description + }); + core.info(`Created label: ${label.name}`); + } else { + throw err; + } + } + } + + core.info(`Label sync complete: ${labels.length} labels synced`); diff --git a/README.md b/README.md index e58422c01..b2d99ea89 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ The Coordinator enforces this. No self-review of rejected work. - [**GitHub Issues Mode**](docs/features/github-issues.md) — Issue-driven development with `gh` CLI integration - [**PRD Mode**](docs/features/prd-mode.md) — Product requirements decomposition into work items - [**Human Team Members**](docs/features/human-team-members.md) — Mixed AI/human teams with routing +- [**Copilot Coding Agent**](docs/features/copilot-coding-agent.md) — Add @copilot for autonomous issue work with capability-based routing - [**Skills System**](docs/features/skills.md) — Earned knowledge with confidence lifecycle - [**Tiered Response Modes**](docs/features/response-modes.md) — Direct/Lightweight/Standard/Full response depth - [**Smart Upgrade**](docs/scenarios/upgrading.md) — Version-aware upgrades with migrations @@ -256,6 +257,7 @@ Labels are auto-created from your team roster via the `sync-squad-labels` workfl |-------|---------| | `squad` | Triage inbox — Lead reviews and assigns | | `squad:{name}` | Assigned to a specific squad member | +| `squad:copilot` | Assigned to @copilot for autonomous coding agent work | Labels sync automatically when `.ai-team/team.md` changes, or you can trigger the workflow manually. diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md new file mode 100644 index 000000000..a98a7d304 --- /dev/null +++ b/docs/features/copilot-coding-agent.md @@ -0,0 +1,148 @@ +# Copilot Coding Agent (@copilot) + +Add the GitHub Copilot coding agent to your Squad as an autonomous team member. It picks up issues, creates branches, and opens PRs — all without a Copilot chat session. + +--- + +## Enabling @copilot + +### New projects (during init) + +Squad asks during team setup: + +``` +> Want to include the Copilot coding agent (@copilot)? +> It can pick up issues autonomously — bug fixes, tests, small features. +``` + +Say **yes**, and @copilot is added to the roster with a default capability profile. + +### Existing projects (after upgrade) + +1. Run the upgrade to get the latest Squad: + +```bash +npx github:bradygaster/squad upgrade +``` + +2. Open a Copilot session with Squad and say: + +``` +> Enable @copilot +``` + +Or manually: + +``` +> Add @copilot to the team +``` + +3. Copy the instructions template so the coding agent knows about your Squad: + +```bash +cp .ai-team-templates/copilot-instructions.md .github/copilot-instructions.md +``` + +Squad will set up the roster entry, capability profile, and routing. + +--- + +## How @copilot Differs from Other Members + +| | AI Agent | Human Member | @copilot | +|---|----------|-------------|----------| +| Badge | āœ… Active | šŸ‘¤ Human | šŸ¤– Coding Agent | +| Name | Cast from universe | Real name | Always "@copilot" | +| Charter | āœ… | āŒ | āŒ (uses `copilot-instructions.md`) | +| Works in session | āœ… | āŒ | āŒ (asynchronous via issue assignment) | +| Spawned by coordinator | āœ… | āŒ | āŒ | +| Creates PRs | Via session commands | Outside Squad | Autonomously | + +--- + +## Capability Profile + +The capability profile in `team.md` defines what @copilot should and shouldn't handle: + +| Tier | Meaning | Examples | +|------|---------|----------| +| 🟢 **Good fit** | Route automatically | Bug fixes, test coverage, lint fixes, dependency updates, small features, docs | +| 🟔 **Needs review** | Route to @copilot but flag for PR review | Medium features with specs, refactoring with tests, API additions | +| šŸ”“ **Not suitable** | Route to a squad member instead | Architecture, multi-system design, security-critical, ambiguous requirements | + +The profile is editable. The Lead can suggest updates based on experience: + +``` +> @copilot nailed that refactoring — bump refactoring to good fit +> That API change needed too much context — keep multi-endpoint work at not suitable +``` + +--- + +## Auto-Assign + +When enabled, the `squad-issue-assign` workflow automatically assigns `@copilot` on the GitHub issue when work is routed to it — so the coding agent picks it up without manual intervention. + +Enable it: +``` +> Turn on @copilot auto-assign +``` + +Disable it: +``` +> Stop auto-assigning to @copilot +``` + +The setting is stored in `team.md` as ``. + +--- + +## Lead Triage + +The Lead evaluates every issue against @copilot's capability profile during triage: + +1. **Good fit?** → Routes to @copilot with reasoning +2. **Needs review?** → Routes to @copilot, flags for squad member PR review +3. **Not suitable?** → Routes to the right squad member, explains why not @copilot + +The Lead can also suggest reassignment in either direction: + +``` +> This test coverage task could go to @copilot — want me to reassign? +> @copilot might struggle with this — suggesting we reassign to Ripley. +``` + +--- + +## Labels + +When @copilot is on the team, the `sync-squad-labels` workflow creates: + +| Label | Color | Purpose | +|-------|-------|---------| +| `squad:copilot` | 🟢 Green | Assigned to @copilot for autonomous work | + +This works alongside the existing `squad` (triage) and `squad:{member}` labels. + +--- + +## copilot-instructions.md + +The `.github/copilot-instructions.md` file gives the coding agent context about your Squad when it works autonomously. It tells @copilot to: + +- Read `team.md` for roster and capability profile +- Read `routing.md` for work routing rules +- Check its capability profile before starting (and request reassignment if the issue doesn't match) +- Follow the `squad/{issue}-{slug}` branch naming convention +- Write decisions to the inbox for the Scribe to merge + +This file is **upgraded automatically** when you run `squad upgrade` and `@copilot` is on your team — even if Squad is already up to date. If @copilot is not enabled, the file is left untouched. + +--- + +## Tips + +- Start conservative with the capability profile and expand as you see what @copilot handles well. +- Use auto-assign for repos where you want fully autonomous issue processing. +- The coding agent works great alongside [issue-driven development](../scenarios/issue-driven-dev.md) — label issues `squad` and the Lead + @copilot handle the rest. +- @copilot's PRs go through normal review — treat them like any team member's work. diff --git a/docs/scenarios/upgrading.md b/docs/scenarios/upgrading.md index 2b4946155..2a8a41aab 100644 --- a/docs/scenarios/upgrading.md +++ b/docs/scenarios/upgrading.md @@ -33,6 +33,8 @@ That's it. |------|----------|-------| | `.github/agents/squad.agent.md` | āœ… Yes | Overwritten with latest coordinator logic | | `.ai-team-templates/` | āœ… Yes | Overwritten with latest templates | +| `.github/workflows/squad-*.yml` | āœ… Yes | Overwritten with latest squad workflows | +| `.github/copilot-instructions.md` | ⚔ Conditional | Updated only if @copilot is enabled on the team | | `.ai-team/` | āŒ Never | Your team's knowledge, decisions, casting state, skills | Squad-owned files (`squad.agent.md` and `.ai-team-templates/`) are replaced entirely. Don't put custom changes in them — they'll be lost on upgrade. diff --git a/index.js b/index.js index 89aff5386..36cd232a6 100644 --- a/index.js +++ b/index.js @@ -415,6 +415,19 @@ if (isUpgrade) { if (isAlreadyCurrent) { // Still run missing migrations in case a prior upgrade was interrupted runMigrations(dest, oldVersion); + + // Even if already current, update copilot-instructions.md if @copilot is enabled + const copilotInstructionsSrc = path.join(root, 'templates', 'copilot-instructions.md'); + const copilotInstructionsDest = path.join(dest, '.github', 'copilot-instructions.md'); + const teamMd = path.join(dest, '.ai-team', 'team.md'); + const copilotEnabled = fs.existsSync(teamMd) + && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); + if (copilotEnabled && fs.existsSync(copilotInstructionsSrc)) { + fs.mkdirSync(path.dirname(copilotInstructionsDest), { recursive: true }); + fs.copyFileSync(copilotInstructionsSrc, copilotInstructionsDest); + console.log(`${GREEN}āœ“${RESET} ${BOLD}upgraded${RESET} .github/copilot-instructions.md`); + } + console.log(`${GREEN}āœ“${RESET} Already up to date (v${pkg.version})`); process.exit(0); } @@ -475,6 +488,32 @@ if (!fs.existsSync(ceremoniesDest)) { console.log(`${DIM}ceremonies.md already exists — skipping${RESET}`); } +// Copy copilot-instructions.md (user-owned on init, upgraded if @copilot is enabled) +const copilotInstructionsSrc = path.join(root, 'templates', 'copilot-instructions.md'); +const copilotInstructionsDest = path.join(dest, '.github', 'copilot-instructions.md'); + +if (!isUpgrade) { + if (fs.existsSync(copilotInstructionsSrc)) { + if (!fs.existsSync(copilotInstructionsDest)) { + fs.mkdirSync(path.dirname(copilotInstructionsDest), { recursive: true }); + fs.copyFileSync(copilotInstructionsSrc, copilotInstructionsDest); + console.log(`${GREEN}āœ“${RESET} .github/copilot-instructions.md`); + } else { + console.log(`${DIM}copilot-instructions.md already exists — skipping${RESET}`); + } + } +} else { + // On upgrade, update copilot-instructions.md if @copilot is enabled on the team + const teamMd = path.join(dest, '.ai-team', 'team.md'); + const copilotEnabled = fs.existsSync(teamMd) + && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); + if (copilotEnabled && fs.existsSync(copilotInstructionsSrc)) { + fs.mkdirSync(path.dirname(copilotInstructionsDest), { recursive: true }); + fs.copyFileSync(copilotInstructionsSrc, copilotInstructionsDest); + console.log(`${GREEN}āœ“${RESET} ${BOLD}upgraded${RESET} .github/copilot-instructions.md`); + } +} + // Append merge=union rules for append-only .ai-team/ files const gitattributes = path.join(dest, '.gitattributes'); const unionRules = [ @@ -546,6 +585,15 @@ if (fs.existsSync(workflowsSrc) && fs.statSync(workflowsSrc).isDirectory()) { if (isUpgrade) { console.log(`\n${DIM}.ai-team/ untouched — your team state is safe${RESET}`); + + // Hint about new features available after upgrade + const teamMd = path.join(dest, '.ai-team', 'team.md'); + const copilotEnabled = fs.existsSync(teamMd) + && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); + if (!copilotEnabled) { + console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); + console.log(` Say ${BOLD}"enable @copilot"${RESET} in your next Squad session to add it to your team.`); + } } console.log(); diff --git a/templates/copilot-instructions.md b/templates/copilot-instructions.md new file mode 100644 index 000000000..7bfa98a32 --- /dev/null +++ b/templates/copilot-instructions.md @@ -0,0 +1,46 @@ +# Copilot Coding Agent — Squad Instructions + +You are working on a project that uses **Squad**, an AI team framework. When picking up issues autonomously, follow these guidelines. + +## Team Context + +Before starting work on any issue: + +1. Read `.ai-team/team.md` for the team roster, member roles, and your capability profile. +2. Read `.ai-team/routing.md` for work routing rules. +3. If the issue has a `squad:{member}` label, read that member's charter at `.ai-team/agents/{member}/charter.md` to understand their domain expertise and coding style — work in their voice. + +## Capability Self-Check + +Before starting work, check your capability profile in `.ai-team/team.md` under the **Coding Agent → Capabilities** section. + +- **🟢 Good fit** — proceed autonomously. +- **🟔 Needs review** — proceed, but note in the PR description that a squad member should review. +- **šŸ”“ Not suitable** — do NOT start work. Instead, comment on the issue: + ``` + šŸ¤– This issue doesn't match my capability profile (reason: {why}). Suggesting reassignment to a squad member. + ``` + +## Branch Naming + +Use the squad branch convention: +``` +squad/{issue-number}-{kebab-case-slug} +``` +Example: `squad/42-fix-login-validation` + +## PR Guidelines + +When opening a PR: +- Reference the issue: `Closes #{issue-number}` +- If the issue had a `squad:{member}` label, mention the member: `Working as {member} ({role})` +- If this is a 🟔 needs-review task, add to the PR description: `āš ļø This task was flagged as "needs review" — please have a squad member review before merging.` +- Follow any project conventions in `.ai-team/decisions.md` + +## Decisions + +If you make a decision that affects other team members, write it to: +``` +.ai-team/decisions/inbox/copilot-{brief-slug}.md +``` +The Scribe will merge it into the shared decisions file. diff --git a/templates/roster.md b/templates/roster.md index d2c0e340c..9e2a8d5cd 100644 --- a/templates/roster.md +++ b/templates/roster.md @@ -18,6 +18,39 @@ | {Name} | {Role} | `.ai-team/agents/{name}/charter.md` | āœ… Active | | Scribe | Session Logger | `.ai-team/agents/scribe/charter.md` | šŸ“‹ Silent | +## Coding Agent + + + +| Name | Role | Charter | Status | +|------|------|---------|--------| +| @copilot | Coding Agent | — | šŸ¤– Coding Agent | + +### Capabilities + +**🟢 Good fit — auto-route when enabled:** +- Bug fixes with clear reproduction steps +- Test coverage (adding missing tests, fixing flaky tests) +- Lint/format fixes and code style cleanup +- Dependency updates and version bumps +- Small isolated features with clear specs +- Boilerplate/scaffolding generation +- Documentation fixes and README updates + +**🟔 Needs review — route to @copilot but flag for squad member PR review:** +- Medium features with clear specs and acceptance criteria +- Refactoring with existing test coverage +- API endpoint additions following established patterns +- Migration scripts with well-defined schemas + +**šŸ”“ Not suitable — route to squad member instead:** +- Architecture decisions and system design +- Multi-system integration requiring coordination +- Ambiguous requirements needing clarification +- Security-critical changes (auth, encryption, access control) +- Performance-critical paths requiring benchmarking +- Changes requiring cross-team discussion + ## Project Context - **Owner:** {user name} ({user email}) diff --git a/templates/routing.md b/templates/routing.md index 65e0e9f45..490b128e1 100644 --- a/templates/routing.md +++ b/templates/routing.md @@ -12,21 +12,35 @@ How to decide who handles what. | Code review | {Name} | Review PRs, check quality, suggest improvements | | Testing | {Name} | Write tests, find edge cases, verify fixes | | Scope & priorities | {Name} | What to build next, trade-offs, decisions | +| Async issue work (bugs, tests, small features) | @copilot šŸ¤– | Well-defined tasks matching capability profile | | Session logging | Scribe | Automatic — never needs routing | ## Issue Routing | Label | Action | Who | |-------|--------|-----| -| `squad` | Triage: analyze issue, assign `squad:{member}` label | Lead | +| `squad` | Triage: analyze issue, evaluate @copilot fit, assign `squad:{member}` label | Lead | | `squad:{name}` | Pick up issue and complete the work | Named member | +| `squad:copilot` | Assign to @copilot for autonomous work (if enabled) | @copilot šŸ¤– | ### How Issue Assignment Works -1. When a GitHub issue gets the `squad` label, the **Lead** triages it — analyzing content, assigning the right `squad:{member}` label, and commenting with triage notes. -2. When a `squad:{member}` label is applied, that member picks up the issue in their next session. -3. Members can reassign by removing their label and adding another member's label. -4. The `squad` label is the "inbox" — untriaged issues waiting for Lead review. +1. When a GitHub issue gets the `squad` label, the **Lead** triages it — analyzing content, evaluating @copilot's capability profile, assigning the right `squad:{member}` label, and commenting with triage notes. +2. **@copilot evaluation:** The Lead checks if the issue matches @copilot's capability profile (🟢 good fit / 🟔 needs review / šŸ”“ not suitable). If it's a good fit, the Lead may route to `squad:copilot` instead of a squad member. +3. When a `squad:{member}` label is applied, that member picks up the issue in their next session. +4. When `squad:copilot` is applied and auto-assign is enabled, `@copilot` is assigned on the issue and picks it up autonomously. +5. Members can reassign by removing their label and adding another member's label. +6. The `squad` label is the "inbox" — untriaged issues waiting for Lead review. + +### Lead Triage Guidance for @copilot + +When triaging, the Lead should ask: + +1. **Is this well-defined?** Clear title, reproduction steps or acceptance criteria, bounded scope → likely 🟢 +2. **Does it follow existing patterns?** Adding a test, fixing a known bug, updating a dependency → likely 🟢 +3. **Does it need design judgment?** Architecture, API design, UX decisions → likely šŸ”“ +4. **Is it security-sensitive?** Auth, encryption, access control → always šŸ”“ +5. **Is it medium complexity with specs?** Feature with clear requirements, refactoring with tests → likely 🟔 ## Rules @@ -37,3 +51,4 @@ How to decide who handles what. 5. **"Team, ..." → fan-out.** Spawn all relevant agents in parallel as `mode: "background"`. 6. **Anticipate downstream work.** If a feature is being built, spawn the tester to write test cases from requirements simultaneously. 7. **Issue-labeled work** — when a `squad:{member}` label is applied to an issue, route to that member. The Lead handles all `squad` (base label) triage. +8. **@copilot routing** — when evaluating issues, check @copilot's capability profile in `team.md`. Route 🟢 good-fit tasks to `squad:copilot`. Flag 🟔 needs-review tasks for PR review. Keep šŸ”“ not-suitable tasks with squad members. diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index cb2ed31be..01632a5d8 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -37,22 +37,32 @@ jobs: const content = fs.readFileSync(teamFile, 'utf8'); const lines = content.split('\n'); + // Check if @copilot auto-assign is enabled + const copilotAutoAssign = content.includes(''); + + // Check if this is a coding agent assignment + const isCopilotAssignment = memberName === 'copilot'; + let assignedMember = null; - let inMembersTable = false; - for (const line of lines) { - if (line.startsWith('## Members')) { - inMembersTable = true; - continue; - } - if (inMembersTable && line.startsWith('## ')) { - break; - } - if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { - const cells = line.split('|').map(c => c.trim()).filter(Boolean); - if (cells.length >= 2 && cells[0].toLowerCase() === memberName) { - assignedMember = { name: cells[0], role: cells[1] }; + if (isCopilotAssignment) { + assignedMember = { name: '@copilot', role: 'Coding Agent' }; + } else { + let inMembersTable = false; + for (const line of lines) { + if (line.startsWith('## Members')) { + inMembersTable = true; + continue; + } + if (inMembersTable && line.startsWith('## ')) { break; } + if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { + const cells = line.split('|').map(c => c.trim()).filter(Boolean); + if (cells.length >= 2 && cells[0].toLowerCase() === memberName) { + assignedMember = { name: cells[0], role: cells[1] }; + break; + } + } } } @@ -67,18 +77,51 @@ jobs: return; } + // Auto-assign @copilot if enabled and this is a copilot assignment + if (isCopilotAssignment && copilotAutoAssign) { + try { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + assignees: ['copilot'] + }); + core.info(`Auto-assigned @copilot to issue #${issue.number}`); + } catch (err) { + core.warning(`Could not auto-assign @copilot: ${err.message}`); + } + } + // Post assignment acknowledgment - const comment = [ - `### šŸ“‹ Assigned to ${assignedMember.name} (${assignedMember.role})`, - '', - `**Issue:** #${issue.number} — ${issue.title}`, - '', - `${assignedMember.name} will pick this up in the next Copilot session.`, - '', - `> **For Copilot coding agent:** If enabled, this issue will be worked automatically.`, - `> Otherwise, start a Copilot session and say:`, - `> \`${assignedMember.name}, work on issue #${issue.number}\``, - ].join('\n'); + let comment; + if (isCopilotAssignment) { + const autoAssignNote = copilotAutoAssign + ? `@copilot has been assigned and will pick this up automatically.` + : `Assign @copilot on this issue to start autonomous work.`; + + comment = [ + `### šŸ¤– Routed to @copilot (Coding Agent)`, + '', + `**Issue:** #${issue.number} — ${issue.title}`, + '', + autoAssignNote, + '', + `> The coding agent will create a \`copilot/*\` branch and open a draft PR.`, + `> Review the PR as you would any team member's work.`, + ].join('\n'); + } else { + comment = [ + `### šŸ“‹ Assigned to ${assignedMember.name} (${assignedMember.role})`, + '', + `**Issue:** #${issue.number} — ${issue.title}`, + '', + `${assignedMember.name} will pick this up in the next Copilot session.`, + '', + `> **For Copilot coding agent:** If enabled, this issue will be worked automatically.`, + `> Otherwise, start a Copilot session and say:`, + `> \`${assignedMember.name}, work on issue #${issue.number}\``, + ].join('\n'); + } await github.rest.issues.createComment({ owner: context.repo.owner, diff --git a/templates/workflows/squad-triage.yml b/templates/workflows/squad-triage.yml index c06f48fd9..fb1c3e19c 100644 --- a/templates/workflows/squad-triage.yml +++ b/templates/workflows/squad-triage.yml @@ -32,6 +32,38 @@ jobs: const content = fs.readFileSync(teamFile, 'utf8'); const lines = content.split('\n'); + // Check if @copilot is on the team + const hasCopilot = content.includes('šŸ¤– Coding Agent'); + const copilotAutoAssign = content.includes(''); + + // Parse @copilot capability profile + let goodFitKeywords = []; + let needsReviewKeywords = []; + let notSuitableKeywords = []; + + if (hasCopilot) { + // Extract capability tiers from team.md + const goodFitMatch = content.match(/🟢\s*Good fit[^:]*:\s*(.+)/i); + const needsReviewMatch = content.match(/🟔\s*Needs review[^:]*:\s*(.+)/i); + const notSuitableMatch = content.match(/šŸ”“\s*Not suitable[^:]*:\s*(.+)/i); + + if (goodFitMatch) { + goodFitKeywords = goodFitMatch[1].toLowerCase().split(',').map(s => s.trim()); + } else { + goodFitKeywords = ['bug fix', 'test coverage', 'lint', 'format', 'dependency update', 'small feature', 'scaffolding', 'doc fix', 'documentation']; + } + if (needsReviewMatch) { + needsReviewKeywords = needsReviewMatch[1].toLowerCase().split(',').map(s => s.trim()); + } else { + needsReviewKeywords = ['medium feature', 'refactoring', 'api endpoint', 'migration']; + } + if (notSuitableMatch) { + notSuitableKeywords = notSuitableMatch[1].toLowerCase().split(',').map(s => s.trim()); + } else { + notSuitableKeywords = ['architecture', 'system design', 'security', 'auth', 'encryption', 'performance']; + } + } + const members = []; let inMembersTable = false; for (const line of lines) { @@ -82,42 +114,65 @@ jobs: let assignedMember = null; let triageReason = ''; - - // Simple keyword-based routing as a baseline - for (const member of members) { - const role = member.role.toLowerCase(); - if ((role.includes('frontend') || role.includes('ui')) && - (issueText.includes('ui') || issueText.includes('frontend') || - issueText.includes('css') || issueText.includes('component') || - issueText.includes('button') || issueText.includes('page') || - issueText.includes('layout') || issueText.includes('design'))) { - assignedMember = member; - triageReason = 'Issue relates to frontend/UI work'; - break; + let copilotTier = null; + + // First, evaluate @copilot fit if enabled + if (hasCopilot) { + const isNotSuitable = notSuitableKeywords.some(kw => issueText.includes(kw)); + const isGoodFit = !isNotSuitable && goodFitKeywords.some(kw => issueText.includes(kw)); + const isNeedsReview = !isNotSuitable && !isGoodFit && needsReviewKeywords.some(kw => issueText.includes(kw)); + + if (isGoodFit) { + copilotTier = 'good-fit'; + assignedMember = { name: '@copilot', role: 'Coding Agent' }; + triageReason = '🟢 Good fit for @copilot — matches capability profile'; + } else if (isNeedsReview) { + copilotTier = 'needs-review'; + assignedMember = { name: '@copilot', role: 'Coding Agent' }; + triageReason = '🟔 Routing to @copilot (needs review) — a squad member should review the PR'; + } else if (isNotSuitable) { + copilotTier = 'not-suitable'; + // Fall through to normal routing } - if ((role.includes('backend') || role.includes('api') || role.includes('server')) && - (issueText.includes('api') || issueText.includes('backend') || - issueText.includes('database') || issueText.includes('endpoint') || - issueText.includes('server') || issueText.includes('auth'))) { - assignedMember = member; - triageReason = 'Issue relates to backend/API work'; - break; - } - if ((role.includes('test') || role.includes('qa') || role.includes('quality')) && - (issueText.includes('test') || issueText.includes('bug') || - issueText.includes('fix') || issueText.includes('regression') || - issueText.includes('coverage'))) { - assignedMember = member; - triageReason = 'Issue relates to testing/quality work'; - break; - } - if ((role.includes('devops') || role.includes('infra') || role.includes('ops')) && - (issueText.includes('deploy') || issueText.includes('ci') || - issueText.includes('pipeline') || issueText.includes('docker') || - issueText.includes('infrastructure'))) { - assignedMember = member; - triageReason = 'Issue relates to DevOps/infrastructure work'; - break; + } + + // If not routed to @copilot, use keyword-based routing + if (!assignedMember) { + for (const member of members) { + const role = member.role.toLowerCase(); + if ((role.includes('frontend') || role.includes('ui')) && + (issueText.includes('ui') || issueText.includes('frontend') || + issueText.includes('css') || issueText.includes('component') || + issueText.includes('button') || issueText.includes('page') || + issueText.includes('layout') || issueText.includes('design'))) { + assignedMember = member; + triageReason = 'Issue relates to frontend/UI work'; + break; + } + if ((role.includes('backend') || role.includes('api') || role.includes('server')) && + (issueText.includes('api') || issueText.includes('backend') || + issueText.includes('database') || issueText.includes('endpoint') || + issueText.includes('server') || issueText.includes('auth'))) { + assignedMember = member; + triageReason = 'Issue relates to backend/API work'; + break; + } + if ((role.includes('test') || role.includes('qa') || role.includes('quality')) && + (issueText.includes('test') || issueText.includes('bug') || + issueText.includes('fix') || issueText.includes('regression') || + issueText.includes('coverage'))) { + assignedMember = member; + triageReason = 'Issue relates to testing/quality work'; + break; + } + if ((role.includes('devops') || role.includes('infra') || role.includes('ops')) && + (issueText.includes('deploy') || issueText.includes('ci') || + issueText.includes('pipeline') || issueText.includes('docker') || + issueText.includes('infrastructure'))) { + assignedMember = member; + triageReason = 'Issue relates to DevOps/infrastructure work'; + break; + } } } @@ -127,7 +182,8 @@ jobs: triageReason = 'No specific domain match — assigned to Lead for further analysis'; } - const assignLabel = `squad:${assignedMember.name.toLowerCase()}`; + const isCopilot = assignedMember.name === '@copilot'; + const assignLabel = isCopilot ? 'squad:copilot' : `squad:${assignedMember.name.toLowerCase()}`; // Add the member-specific label await github.rest.issues.addLabels({ @@ -137,6 +193,30 @@ jobs: labels: [assignLabel] }); + // Auto-assign @copilot if enabled + if (isCopilot && copilotAutoAssign) { + try { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + assignees: ['copilot'] + }); + } catch (err) { + core.warning(`Could not auto-assign @copilot: ${err.message}`); + } + } + + // Build copilot evaluation note + let copilotNote = ''; + if (hasCopilot && !isCopilot) { + if (copilotTier === 'not-suitable') { + copilotNote = `\n\n**@copilot evaluation:** šŸ”“ Not suitable — issue involves work outside the coding agent's capability profile.`; + } else { + copilotNote = `\n\n**@copilot evaluation:** No strong capability match — routed to squad member.`; + } + } + // Post triage comment const comment = [ `### šŸ—ļø Squad Triage — ${lead.name} (${lead.role})`, @@ -144,14 +224,17 @@ jobs: `**Issue:** #${issue.number} — ${issue.title}`, `**Assigned to:** ${assignedMember.name} (${assignedMember.role})`, `**Reason:** ${triageReason}`, + copilotTier === 'needs-review' ? `\nāš ļø **PR review recommended** — a squad member should review @copilot's work on this one.` : '', + copilotNote, '', `---`, '', `**Team roster:**`, memberList, + hasCopilot ? `- **@copilot** (Coding Agent) → label: \`squad:copilot\`` : '', '', `> To reassign, remove the current \`squad:*\` label and add the correct one.`, - ].join('\n'); + ].filter(Boolean).join('\n'); await github.rest.issues.createComment({ owner: context.repo.owner, diff --git a/templates/workflows/sync-squad-labels.yml b/templates/workflows/sync-squad-labels.yml index 58fe5c584..67b730732 100644 --- a/templates/workflows/sync-squad-labels.yml +++ b/templates/workflows/sync-squad-labels.yml @@ -55,9 +55,13 @@ jobs: core.info(`Found ${members.length} squad members: ${members.map(m => m.name).join(', ')}`); + // Check if @copilot is on the team + const hasCopilot = content.includes('šŸ¤– Coding Agent'); + // Define label color palette for squad labels const SQUAD_COLOR = '6366f1'; const MEMBER_COLOR = '3b82f6'; + const COPILOT_COLOR = '10b981'; // Ensure the base "squad" triage label exists const labels = [ @@ -72,6 +76,15 @@ jobs: }); } + // Add @copilot label if coding agent is on the team + if (hasCopilot) { + labels.push({ + name: 'squad:copilot', + color: COPILOT_COLOR, + description: 'Assigned to @copilot (Coding Agent) for autonomous work' + }); + } + // Sync labels (create or update) for (const label of labels) { try { diff --git a/test/index.test.js b/test/index.test.js index 5e918dca4..8ec280fb5 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -543,3 +543,68 @@ describe('edge cases', () => { assert.equal(result.exitCode, 0, 're-init should exit 0'); }); }); + +describe('copilot-instructions.md', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-copilot-')); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('creates .github/copilot-instructions.md on init', () => { + runInit(tmpDir); + const dest = path.join(tmpDir, '.github', 'copilot-instructions.md'); + assert.ok(fs.existsSync(dest), 'copilot-instructions.md should be created'); + const content = fs.readFileSync(dest, 'utf8'); + assert.ok(content.includes('Squad'), 'should reference Squad'); + assert.ok(content.includes('.ai-team/team.md'), 'should reference team.md'); + assert.ok(content.includes('Capability Self-Check'), 'should include capability self-check'); + }); + + it('skips copilot-instructions.md when it already exists', () => { + // Create the file first + fs.mkdirSync(path.join(tmpDir, '.github'), { recursive: true }); + fs.writeFileSync(path.join(tmpDir, '.github', 'copilot-instructions.md'), 'custom content'); + runInit(tmpDir); + const content = fs.readFileSync(path.join(tmpDir, '.github', 'copilot-instructions.md'), 'utf8'); + assert.equal(content, 'custom content', 'should not overwrite existing file'); + }); + + it('does not overwrite copilot-instructions.md on upgrade when @copilot is not enabled', () => { + runInit(tmpDir); + // Customize the file + const dest = path.join(tmpDir, '.github', 'copilot-instructions.md'); + fs.writeFileSync(dest, 'user customized'); + runCmd(tmpDir, 'upgrade'); + const content = fs.readFileSync(dest, 'utf8'); + assert.equal(content, 'user customized', 'upgrade should not touch copilot-instructions.md when @copilot is not enabled'); + }); + + it('upgrades copilot-instructions.md when @copilot is enabled on the team', () => { + runInit(tmpDir); + // Simulate @copilot being enabled in team.md + const teamDir = path.join(tmpDir, '.ai-team'); + fs.mkdirSync(teamDir, { recursive: true }); + fs.writeFileSync(path.join(teamDir, 'team.md'), '| @copilot | Coding Agent | — | šŸ¤– Coding Agent |'); + // Customize the instructions file + const dest = path.join(tmpDir, '.github', 'copilot-instructions.md'); + fs.writeFileSync(dest, 'old version'); + runCmd(tmpDir, 'upgrade'); + const content = fs.readFileSync(dest, 'utf8'); + assert.notEqual(content, 'old version', 'upgrade should overwrite copilot-instructions.md when @copilot is enabled'); + assert.ok(content.includes('Squad'), 'should contain latest template content'); + }); + + it('copilot-instructions.md content matches source template', () => { + runInit(tmpDir); + const dest = path.join(tmpDir, '.github', 'copilot-instructions.md'); + const src = path.join(ROOT, 'templates', 'copilot-instructions.md'); + const destContent = fs.readFileSync(dest, 'utf8'); + const srcContent = fs.readFileSync(src, 'utf8'); + assert.equal(destContent, srcContent, 'init should copy template exactly'); + }); +}); From 6b254cbd6e09a24e703bb13158bae19ec08c51d8 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 20:13:38 -0500 Subject: [PATCH 02/33] fix: stronger intercept rule for 'Enable @copilot' command Move @copilot command detection to a mandatory pre-check table at the very top of Team Mode, before any routing or interpretation. Explicit STOP instructions prevent the coordinator from treating the command as a file reference or session greeting. --- .github/agents/squad.agent.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 7c58d060c..b38c0b528 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -112,9 +112,23 @@ The `union` merge driver keeps all lines from both sides, which is correct for a ## Team Mode -**āš ļø CRITICAL RULE: Every agent interaction MUST use the `task` tool to spawn a real agent. You MUST call the `task` tool — never simulate, role-play, or inline an agent's work. If you did not call the `task` tool, the agent was NOT spawned. No exceptions.** +### āš ļø MANDATORY PRE-CHECK — Run Before Anything Else + +**Before interpreting ANY user message, check these exact-match triggers IN ORDER. If one matches, follow its action IMMEDIATELY — do not continue to routing, do not treat as a greeting, do not reference files or folders.** + +| If user message contains (case-insensitive) | Action | +|----------------------------------------------|--------| +| "enable @copilot" OR "enable copilot" | **STOP.** Follow the **Copilot Coding Agent Member** section to add @copilot to the roster. This is NOT about a file or folder named "copilot". | +| "add @copilot" OR "add coding agent" | **STOP.** Follow the **Copilot Coding Agent Member** section. | +| "disable @copilot" OR "remove coding agent" | **STOP.** Follow the **Copilot Coding Agent Member** section to remove @copilot. | +| "update @copilot capabilities" | **STOP.** Follow the **Copilot Coding Agent Member** section to edit the capability profile. | +| "@copilot auto-assign" OR "turn on @copilot" | **STOP.** Set `` in team.md. | + +**Only if NONE of the above matched**, proceed to the normal routing table below. -**āš ļø INTERCEPT RULE — @copilot commands:** If the user's message contains "enable @copilot", "add @copilot", "add coding agent", "disable @copilot", "remove coding agent", or "update @copilot capabilities" — this is a **team management command**, NOT a session greeting, NOT a file reference, NOT a status check. Immediately follow the **Copilot Coding Agent Member** section to add/remove/update @copilot on the roster. Do not interpret "@copilot" as a file path or project reference. +--- + +**āš ļø CRITICAL RULE: Every agent interaction MUST use the `task` tool to spawn a real agent. You MUST call the `task` tool — never simulate, role-play, or inline an agent's work. If you did not call the `task` tool, the agent was NOT spawned. No exceptions.** **On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.ai-team/` paths must be resolved relative to it. Pass the team root into every spawn prompt as `TEAM_ROOT` and the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. From c896eb3b2841a3e32064896aff8b10e58fb201df Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 20:16:12 -0500 Subject: [PATCH 03/33] fix: rename triggers from '@copilot' to 'coding-agent' Typing '@copilot' in Copilot chat triggers IDE file-tagging, preventing the coordinator from seeing the command text. Changed all trigger phrases to use 'coding-agent' which is unambiguous and won't collide with file/folder names. --- .github/agents/squad.agent.md | 26 +++++++++++++------------- docs/features/copilot-coding-agent.md | 10 +++++----- index.js | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index b38c0b528..b68f99ec5 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -98,7 +98,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a → If yes, follow the GitHub Issues Mode flow to connect and list the backlog. - *"Are any humans joining the team? (names and roles, or just AI for now)"* → If yes, add human members to the roster per the Human Team Members section. - - *"Want to include the Copilot coding agent (@copilot)? It can pick up issues autonomously — bug fixes, tests, small features. (yes/no)"* + - *"Want to include the Copilot coding agent? It can pick up issues autonomously — bug fixes, tests, small features. Say 'enable coding-agent' anytime to add it. (yes/no)"* → If yes, follow the Copilot Coding Agent Member section to add @copilot to the roster. → Also ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/always for good-fit issues/no)"* → Generate the default capability profile (good fit / needs review / not suitable) and let the user customize. @@ -118,11 +118,11 @@ The `union` merge driver keeps all lines from both sides, which is correct for a | If user message contains (case-insensitive) | Action | |----------------------------------------------|--------| -| "enable @copilot" OR "enable copilot" | **STOP.** Follow the **Copilot Coding Agent Member** section to add @copilot to the roster. This is NOT about a file or folder named "copilot". | -| "add @copilot" OR "add coding agent" | **STOP.** Follow the **Copilot Coding Agent Member** section. | -| "disable @copilot" OR "remove coding agent" | **STOP.** Follow the **Copilot Coding Agent Member** section to remove @copilot. | -| "update @copilot capabilities" | **STOP.** Follow the **Copilot Coding Agent Member** section to edit the capability profile. | -| "@copilot auto-assign" OR "turn on @copilot" | **STOP.** Set `` in team.md. | +| "enable coding-agent" OR "enable coding agent" | **STOP.** Follow the **Copilot Coding Agent Member** section to add the coding agent to the roster. | +| "add coding-agent" OR "add coding agent" OR "add coding agent to the team" | **STOP.** Follow the **Copilot Coding Agent Member** section. | +| "disable coding-agent" OR "remove coding-agent" OR "remove coding agent" | **STOP.** Follow the **Copilot Coding Agent Member** section to remove the coding agent. | +| "update coding-agent capabilities" OR "change what coding-agent can do" | **STOP.** Follow the **Copilot Coding Agent Member** section to edit the capability profile. | +| "coding-agent auto-assign" OR "turn on coding-agent auto-assign" | **STOP.** Set `` in team.md. | **Only if NONE of the above matched**, proceed to the normal routing table below. @@ -216,7 +216,7 @@ The routing table determines **WHO** handles work. After routing, use Response M |--------|--------| | Names someone ("Ripley, fix the button") | Spawn that agent | | "Team" or multi-domain question | Spawn 2-3+ relevant agents in parallel, synthesize | -| Coding agent management ("enable @copilot", "add coding agent", "disable @copilot", "update @copilot capabilities") | Follow Copilot Coding Agent Member (see that section) — **do not treat as a status check** | +| Coding agent management ("enable coding-agent", "add coding-agent", "disable coding-agent", "update coding-agent capabilities") | Follow Copilot Coding Agent Member (see that section) — **do not treat as a status check** | | Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | | Issue suitable for @copilot ("this looks like a @copilot task", "@copilot could handle this") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | | Ceremony request ("design meeting", "run a retro") | Run the matching ceremony from `ceremonies.md` (see Ceremonies) | @@ -1516,12 +1516,12 @@ The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous | User says | Action | |-----------|--------| -| "enable @copilot" / "add coding agent" / "add @copilot to the team" | Add @copilot to roster with capability profile | -| "disable @copilot" / "remove coding agent" | Remove @copilot from roster | -| "update @copilot capabilities" / "change what @copilot can do" | Edit the capability profile in team.md | -| "auto-assign issues to @copilot" / "turn on @copilot auto-assign" | Set `` in team.md | -| "stop auto-assigning to @copilot" | Set `` in team.md | -| "@copilot can handle this" / "route to coding agent" | Route current issue to @copilot | +| "enable coding-agent" / "add coding-agent" / "add coding agent to the team" | Add @copilot to roster with capability profile | +| "disable coding-agent" / "remove coding-agent" | Remove @copilot from roster | +| "update coding-agent capabilities" / "change what coding-agent can do" | Edit the capability profile in team.md | +| "coding-agent auto-assign" / "turn on coding-agent auto-assign" | Set `` in team.md | +| "stop coding-agent auto-assign" | Set `` in team.md | +| "coding-agent can handle this" / "route to coding agent" | Route current issue to @copilot | ### How the Coding Agent Differs diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index a98a7d304..5d4ded14f 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -28,13 +28,13 @@ npx github:bradygaster/squad upgrade 2. Open a Copilot session with Squad and say: ``` -> Enable @copilot +> enable coding-agent ``` -Or manually: +Or: ``` -> Add @copilot to the team +> add coding-agent ``` 3. Copy the instructions template so the coding agent knows about your Squad: @@ -85,12 +85,12 @@ When enabled, the `squad-issue-assign` workflow automatically assigns `@copilot` Enable it: ``` -> Turn on @copilot auto-assign +> turn on coding-agent auto-assign ``` Disable it: ``` -> Stop auto-assigning to @copilot +> stop coding-agent auto-assign ``` The setting is stored in `team.md` as ``. diff --git a/index.js b/index.js index 36cd232a6..924f08474 100644 --- a/index.js +++ b/index.js @@ -592,7 +592,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Say ${BOLD}"enable @copilot"${RESET} in your next Squad session to add it to your team.`); + console.log(` Say ${BOLD}"enable coding-agent"${RESET} in your next Squad session to add it to your team.`); } } From 325743b5bbde857d44d1a262b2f0313670a26d4b Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 20:19:12 -0500 Subject: [PATCH 04/33] fix: use 'hire copilot' / 'squad enable copilot' as triggers '@copilot' triggered IDE file-tagging, 'coding-agent' triggered Azure MCP tools (azd coding-agent config). New triggers use squad-prefixed phrases or 'hire copilot' which are unambiguous. --- .github/agents/squad.agent.md | 28 +++++++++++++-------------- docs/features/copilot-coding-agent.md | 8 ++++---- index.js | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index b68f99ec5..fc5534e3f 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -98,7 +98,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a → If yes, follow the GitHub Issues Mode flow to connect and list the backlog. - *"Are any humans joining the team? (names and roles, or just AI for now)"* → If yes, add human members to the roster per the Human Team Members section. - - *"Want to include the Copilot coding agent? It can pick up issues autonomously — bug fixes, tests, small features. Say 'enable coding-agent' anytime to add it. (yes/no)"* + - *"Want to include the Copilot coding agent? It can pick up issues autonomously — bug fixes, tests, small features. Say 'hire copilot' anytime to add it. (yes/no)"* → If yes, follow the Copilot Coding Agent Member section to add @copilot to the roster. → Also ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/always for good-fit issues/no)"* → Generate the default capability profile (good fit / needs review / not suitable) and let the user customize. @@ -114,15 +114,15 @@ The `union` merge driver keeps all lines from both sides, which is correct for a ### āš ļø MANDATORY PRE-CHECK — Run Before Anything Else -**Before interpreting ANY user message, check these exact-match triggers IN ORDER. If one matches, follow its action IMMEDIATELY — do not continue to routing, do not treat as a greeting, do not reference files or folders.** +**Before interpreting ANY user message, check these exact-match triggers IN ORDER. If one matches, follow its action IMMEDIATELY — do not continue to routing, do not treat as a greeting, do not reference files or folders, do not call Azure tools.** | If user message contains (case-insensitive) | Action | |----------------------------------------------|--------| -| "enable coding-agent" OR "enable coding agent" | **STOP.** Follow the **Copilot Coding Agent Member** section to add the coding agent to the roster. | -| "add coding-agent" OR "add coding agent" OR "add coding agent to the team" | **STOP.** Follow the **Copilot Coding Agent Member** section. | -| "disable coding-agent" OR "remove coding-agent" OR "remove coding agent" | **STOP.** Follow the **Copilot Coding Agent Member** section to remove the coding agent. | -| "update coding-agent capabilities" OR "change what coding-agent can do" | **STOP.** Follow the **Copilot Coding Agent Member** section to edit the capability profile. | -| "coding-agent auto-assign" OR "turn on coding-agent auto-assign" | **STOP.** Set `` in team.md. | +| "squad enable copilot" OR "squad add copilot" | **STOP.** Follow the **Copilot Coding Agent Member** section to add @copilot to the roster. Do NOT call Azure tools. | +| "squad disable copilot" OR "squad remove copilot" | **STOP.** Follow the **Copilot Coding Agent Member** section to remove @copilot. | +| "squad copilot capabilities" OR "update copilot capabilities" | **STOP.** Follow the **Copilot Coding Agent Member** section to edit the capability profile. | +| "squad copilot auto-assign" | **STOP.** Set `` in team.md. | +| "hire copilot" | **STOP.** Follow the **Copilot Coding Agent Member** section to add @copilot to the roster. | **Only if NONE of the above matched**, proceed to the normal routing table below. @@ -216,7 +216,7 @@ The routing table determines **WHO** handles work. After routing, use Response M |--------|--------| | Names someone ("Ripley, fix the button") | Spawn that agent | | "Team" or multi-domain question | Spawn 2-3+ relevant agents in parallel, synthesize | -| Coding agent management ("enable coding-agent", "add coding-agent", "disable coding-agent", "update coding-agent capabilities") | Follow Copilot Coding Agent Member (see that section) — **do not treat as a status check** | +| Coding agent management ("squad enable copilot", "hire copilot", "squad disable copilot") | Follow Copilot Coding Agent Member (see that section) — **do not treat as a status check, do not call Azure tools** | | Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | | Issue suitable for @copilot ("this looks like a @copilot task", "@copilot could handle this") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | | Ceremony request ("design meeting", "run a retro") | Run the matching ceremony from `ceremonies.md` (see Ceremonies) | @@ -1516,12 +1516,12 @@ The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous | User says | Action | |-----------|--------| -| "enable coding-agent" / "add coding-agent" / "add coding agent to the team" | Add @copilot to roster with capability profile | -| "disable coding-agent" / "remove coding-agent" | Remove @copilot from roster | -| "update coding-agent capabilities" / "change what coding-agent can do" | Edit the capability profile in team.md | -| "coding-agent auto-assign" / "turn on coding-agent auto-assign" | Set `` in team.md | -| "stop coding-agent auto-assign" | Set `` in team.md | -| "coding-agent can handle this" / "route to coding agent" | Route current issue to @copilot | +| "squad enable copilot" / "hire copilot" / "squad add copilot" | Add @copilot to roster with capability profile | +| "squad disable copilot" / "squad remove copilot" | Remove @copilot from roster | +| "squad copilot capabilities" / "update copilot capabilities" | Edit the capability profile in team.md | +| "squad copilot auto-assign" | Set `` in team.md | +| "stop copilot auto-assign" | Set `` in team.md | +| "copilot can handle this" / "route to copilot" | Route current issue to @copilot | ### How the Coding Agent Differs diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 5d4ded14f..e543a30f2 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -28,13 +28,13 @@ npx github:bradygaster/squad upgrade 2. Open a Copilot session with Squad and say: ``` -> enable coding-agent +> hire copilot ``` Or: ``` -> add coding-agent +> squad enable copilot ``` 3. Copy the instructions template so the coding agent knows about your Squad: @@ -85,12 +85,12 @@ When enabled, the `squad-issue-assign` workflow automatically assigns `@copilot` Enable it: ``` -> turn on coding-agent auto-assign +> squad copilot auto-assign ``` Disable it: ``` -> stop coding-agent auto-assign +> stop copilot auto-assign ``` The setting is stored in `team.md` as ``. diff --git a/index.js b/index.js index 924f08474..f4ed886a0 100644 --- a/index.js +++ b/index.js @@ -592,7 +592,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Say ${BOLD}"enable coding-agent"${RESET} in your next Squad session to add it to your team.`); + console.log(` Say ${BOLD}"hire copilot"${RESET} in your next Squad session to add it to your team.`); } } From bc1e40bfa45b02c0af3125b08cf25ed6f4c000d6 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 20:22:48 -0500 Subject: [PATCH 05/33] fix: move copilot command check above Team Mode, use 'add copilot to squad' - Moved trigger check to top-level (before Init/Team Mode split) so it runs before any routing or interpretation - Primary trigger: 'add copilot to squad' - unambiguous, won't trigger IDE file-tagging (@) or Azure tools (coding-agent) - Previous triggers failed: '@copilot' (IDE intercept), 'coding-agent' (Azure MCP), 'hire copilot' (confused with existing team hiring language) --- .github/agents/squad.agent.md | 42 ++++++++++++--------------- docs/features/copilot-coding-agent.md | 10 ++----- index.js | 2 +- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index fc5534e3f..71736d2a6 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -20,7 +20,19 @@ You are **Squad (Coordinator)** — the orchestrator for this project's AI team. Check: Does `.ai-team/team.md` exist? - **No** → Init Mode -- **Yes** → Team Mode +- **Yes** → **First, run the Copilot Coding Agent check below**, then Team Mode + +### Copilot Coding Agent — Command Check + +**BEFORE entering Team Mode, check the user's message for these phrases. If ANY match, this is a team management command — follow the Copilot Coding Agent Member section to modify the roster. Do NOT treat it as a greeting, status check, file reference, or Azure operation.** + +**Trigger phrases (case-insensitive):** +- `"add copilot to squad"` → Add @copilot to the roster with capability profile +- `"remove copilot from squad"` → Remove @copilot from the roster +- `"copilot capabilities"` → Edit the capability profile in team.md +- `"copilot auto-assign"` → Set `` in team.md + +If the user's message does NOT contain any of these phrases, proceed to Team Mode normally. --- @@ -98,7 +110,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a → If yes, follow the GitHub Issues Mode flow to connect and list the backlog. - *"Are any humans joining the team? (names and roles, or just AI for now)"* → If yes, add human members to the roster per the Human Team Members section. - - *"Want to include the Copilot coding agent? It can pick up issues autonomously — bug fixes, tests, small features. Say 'hire copilot' anytime to add it. (yes/no)"* + - *"Want to include the Copilot coding agent? It can pick up issues autonomously — bug fixes, tests, small features. Say 'add copilot to squad' anytime to add it. (yes/no)"* → If yes, follow the Copilot Coding Agent Member section to add @copilot to the roster. → Also ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/always for good-fit issues/no)"* → Generate the default capability profile (good fit / needs review / not suitable) and let the user customize. @@ -112,22 +124,6 @@ The `union` merge driver keeps all lines from both sides, which is correct for a ## Team Mode -### āš ļø MANDATORY PRE-CHECK — Run Before Anything Else - -**Before interpreting ANY user message, check these exact-match triggers IN ORDER. If one matches, follow its action IMMEDIATELY — do not continue to routing, do not treat as a greeting, do not reference files or folders, do not call Azure tools.** - -| If user message contains (case-insensitive) | Action | -|----------------------------------------------|--------| -| "squad enable copilot" OR "squad add copilot" | **STOP.** Follow the **Copilot Coding Agent Member** section to add @copilot to the roster. Do NOT call Azure tools. | -| "squad disable copilot" OR "squad remove copilot" | **STOP.** Follow the **Copilot Coding Agent Member** section to remove @copilot. | -| "squad copilot capabilities" OR "update copilot capabilities" | **STOP.** Follow the **Copilot Coding Agent Member** section to edit the capability profile. | -| "squad copilot auto-assign" | **STOP.** Set `` in team.md. | -| "hire copilot" | **STOP.** Follow the **Copilot Coding Agent Member** section to add @copilot to the roster. | - -**Only if NONE of the above matched**, proceed to the normal routing table below. - ---- - **āš ļø CRITICAL RULE: Every agent interaction MUST use the `task` tool to spawn a real agent. You MUST call the `task` tool — never simulate, role-play, or inline an agent's work. If you did not call the `task` tool, the agent was NOT spawned. No exceptions.** **On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.ai-team/` paths must be resolved relative to it. Pass the team root into every spawn prompt as `TEAM_ROOT` and the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. @@ -216,7 +212,7 @@ The routing table determines **WHO** handles work. After routing, use Response M |--------|--------| | Names someone ("Ripley, fix the button") | Spawn that agent | | "Team" or multi-domain question | Spawn 2-3+ relevant agents in parallel, synthesize | -| Coding agent management ("squad enable copilot", "hire copilot", "squad disable copilot") | Follow Copilot Coding Agent Member (see that section) — **do not treat as a status check, do not call Azure tools** | +| Coding agent management ("add copilot to squad", "remove copilot from squad") | Follow Copilot Coding Agent Member (see that section) | | Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | | Issue suitable for @copilot ("this looks like a @copilot task", "@copilot could handle this") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | | Ceremony request ("design meeting", "run a retro") | Run the matching ceremony from `ceremonies.md` (see Ceremonies) | @@ -1516,10 +1512,10 @@ The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous | User says | Action | |-----------|--------| -| "squad enable copilot" / "hire copilot" / "squad add copilot" | Add @copilot to roster with capability profile | -| "squad disable copilot" / "squad remove copilot" | Remove @copilot from roster | -| "squad copilot capabilities" / "update copilot capabilities" | Edit the capability profile in team.md | -| "squad copilot auto-assign" | Set `` in team.md | +| "add copilot to squad" / "add copilot to the team" | Add @copilot to roster with capability profile | +| "remove copilot from squad" / "remove copilot from the team" | Remove @copilot from roster | +| "copilot capabilities" / "update copilot capabilities" | Edit the capability profile in team.md | +| "copilot auto-assign" / "turn on copilot auto-assign" | Set `` in team.md | | "stop copilot auto-assign" | Set `` in team.md | | "copilot can handle this" / "route to copilot" | Route current issue to @copilot | diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index e543a30f2..20780ab3e 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -28,13 +28,7 @@ npx github:bradygaster/squad upgrade 2. Open a Copilot session with Squad and say: ``` -> hire copilot -``` - -Or: - -``` -> squad enable copilot +> add copilot to squad ``` 3. Copy the instructions template so the coding agent knows about your Squad: @@ -85,7 +79,7 @@ When enabled, the `squad-issue-assign` workflow automatically assigns `@copilot` Enable it: ``` -> squad copilot auto-assign +> copilot auto-assign ``` Disable it: diff --git a/index.js b/index.js index f4ed886a0..101498d20 100644 --- a/index.js +++ b/index.js @@ -592,7 +592,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Say ${BOLD}"hire copilot"${RESET} in your next Squad session to add it to your team.`); + console.log(` Say ${BOLD}"add copilot to squad"${RESET} in your next Squad session to add it to your team.`); } } From 118bac8d0bb4960cef01bc5fd75d2f5442022f96 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 20:26:54 -0500 Subject: [PATCH 06/33] fix: use 'add copilot agent' to avoid cast member confusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'add copilot to squad' was interpreted as adding a new cast member named Copilot. Adding 'agent' disambiguates — it's clearly about the GitHub Copilot coding agent, not a person. Also added explicit 'NOT a new cast member' warnings in the command check and routing table. --- .github/agents/squad.agent.md | 26 +++++++++++++------------- docs/features/copilot-coding-agent.md | 6 +++--- index.js | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 71736d2a6..8dce3b500 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -24,13 +24,13 @@ Check: Does `.ai-team/team.md` exist? ### Copilot Coding Agent — Command Check -**BEFORE entering Team Mode, check the user's message for these phrases. If ANY match, this is a team management command — follow the Copilot Coding Agent Member section to modify the roster. Do NOT treat it as a greeting, status check, file reference, or Azure operation.** +**BEFORE entering Team Mode, check the user's message for these phrases. If ANY match, this is NOT about adding a new cast member — it is about enabling the GitHub Copilot coding agent (@copilot) which works autonomously on issues. Follow the Copilot Coding Agent Member section. Do NOT create a new agent, do NOT cast a name, do NOT call Azure tools.** **Trigger phrases (case-insensitive):** -- `"add copilot to squad"` → Add @copilot to the roster with capability profile -- `"remove copilot from squad"` → Remove @copilot from the roster -- `"copilot capabilities"` → Edit the capability profile in team.md -- `"copilot auto-assign"` → Set `` in team.md +- `"add copilot agent"` → Add @copilot (the GitHub coding agent) to the roster with capability profile. This is NOT a new cast member — it is the GitHub Copilot coding agent that autonomously works on issues. +- `"remove copilot agent"` → Remove @copilot from the roster +- `"copilot agent capabilities"` → Edit the capability profile in team.md +- `"copilot agent auto-assign"` → Set `` in team.md If the user's message does NOT contain any of these phrases, proceed to Team Mode normally. @@ -110,7 +110,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a → If yes, follow the GitHub Issues Mode flow to connect and list the backlog. - *"Are any humans joining the team? (names and roles, or just AI for now)"* → If yes, add human members to the roster per the Human Team Members section. - - *"Want to include the Copilot coding agent? It can pick up issues autonomously — bug fixes, tests, small features. Say 'add copilot to squad' anytime to add it. (yes/no)"* + - *"Want to include the Copilot coding agent? It can pick up issues autonomously — bug fixes, tests, small features. Say 'add copilot agent' anytime to add it. (yes/no)"* → If yes, follow the Copilot Coding Agent Member section to add @copilot to the roster. → Also ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/always for good-fit issues/no)"* → Generate the default capability profile (good fit / needs review / not suitable) and let the user customize. @@ -212,7 +212,7 @@ The routing table determines **WHO** handles work. After routing, use Response M |--------|--------| | Names someone ("Ripley, fix the button") | Spawn that agent | | "Team" or multi-domain question | Spawn 2-3+ relevant agents in parallel, synthesize | -| Coding agent management ("add copilot to squad", "remove copilot from squad") | Follow Copilot Coding Agent Member (see that section) | +| Coding agent management ("add copilot agent", "remove copilot agent") | Follow Copilot Coding Agent Member (see that section) — this is the GitHub coding agent, NOT a new cast member | | Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | | Issue suitable for @copilot ("this looks like a @copilot task", "@copilot could handle this") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | | Ceremony request ("design meeting", "run a retro") | Run the matching ceremony from `ceremonies.md` (see Ceremonies) | @@ -1512,12 +1512,12 @@ The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous | User says | Action | |-----------|--------| -| "add copilot to squad" / "add copilot to the team" | Add @copilot to roster with capability profile | -| "remove copilot from squad" / "remove copilot from the team" | Remove @copilot from roster | -| "copilot capabilities" / "update copilot capabilities" | Edit the capability profile in team.md | -| "copilot auto-assign" / "turn on copilot auto-assign" | Set `` in team.md | -| "stop copilot auto-assign" | Set `` in team.md | -| "copilot can handle this" / "route to copilot" | Route current issue to @copilot | +| "add copilot agent" / "add copilot agent to the team" | Add @copilot to roster with capability profile | +| "remove copilot agent" / "remove copilot agent from the team" | Remove @copilot from roster | +| "copilot agent capabilities" / "update copilot agent capabilities" | Edit the capability profile in team.md | +| "copilot agent auto-assign" / "turn on copilot agent auto-assign" | Set `` in team.md | +| "stop copilot agent auto-assign" | Set `` in team.md | +| "copilot agent can handle this" / "route to copilot agent" | Route current issue to @copilot | ### How the Coding Agent Differs diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 20780ab3e..40ac71896 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -28,7 +28,7 @@ npx github:bradygaster/squad upgrade 2. Open a Copilot session with Squad and say: ``` -> add copilot to squad +> add copilot agent ``` 3. Copy the instructions template so the coding agent knows about your Squad: @@ -79,12 +79,12 @@ When enabled, the `squad-issue-assign` workflow automatically assigns `@copilot` Enable it: ``` -> copilot auto-assign +> copilot agent auto-assign ``` Disable it: ``` -> stop copilot auto-assign +> stop copilot agent auto-assign ``` The setting is stored in `team.md` as ``. diff --git a/index.js b/index.js index 101498d20..c0890bc88 100644 --- a/index.js +++ b/index.js @@ -592,7 +592,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Say ${BOLD}"add copilot to squad"${RESET} in your next Squad session to add it to your team.`); + console.log(` Say ${BOLD}"add copilot agent"${RESET} in your next Squad session to add it to your team.`); } } From a7fc6ad81029556c881b31a40e600e15338b8d92 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 20:34:16 -0500 Subject: [PATCH 07/33] feat: replace chat triggers with CLI copilot subcommand - Add 'squad copilot' subcommand (--auto-assign, --off flags) - Remove unreliable agent instruction triggers - CLI directly modifies team.md and copies copilot-instructions.md - Update agent.md to reference CLI instead of chat commands - Rewrite tests for copilot subcommand (44/44 passing) - Update feature docs with CLI usage --- .github/agents/squad.agent.md | 66 ++++++------- docs/features/copilot-coding-agent.md | 44 +++------ index.js | 135 +++++++++++++++++++++++--- test/index.test.js | 92 ++++++++++++++---- 4 files changed, 233 insertions(+), 104 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 8dce3b500..5c2466c60 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -20,19 +20,11 @@ You are **Squad (Coordinator)** — the orchestrator for this project's AI team. Check: Does `.ai-team/team.md` exist? - **No** → Init Mode -- **Yes** → **First, run the Copilot Coding Agent check below**, then Team Mode +- **Yes** → Team Mode -### Copilot Coding Agent — Command Check +### Adding the Copilot Coding Agent -**BEFORE entering Team Mode, check the user's message for these phrases. If ANY match, this is NOT about adding a new cast member — it is about enabling the GitHub Copilot coding agent (@copilot) which works autonomously on issues. Follow the Copilot Coding Agent Member section. Do NOT create a new agent, do NOT cast a name, do NOT call Azure tools.** - -**Trigger phrases (case-insensitive):** -- `"add copilot agent"` → Add @copilot (the GitHub coding agent) to the roster with capability profile. This is NOT a new cast member — it is the GitHub Copilot coding agent that autonomously works on issues. -- `"remove copilot agent"` → Remove @copilot from the roster -- `"copilot agent capabilities"` → Edit the capability profile in team.md -- `"copilot agent auto-assign"` → Set `` in team.md - -If the user's message does NOT contain any of these phrases, proceed to Team Mode normally. +The coding agent is added via the CLI: `npx github:bradygaster/squad copilot`. Once added, the Copilot Coding Agent Member section describes how it works with the team. --- @@ -110,15 +102,13 @@ The `union` merge driver keeps all lines from both sides, which is correct for a → If yes, follow the GitHub Issues Mode flow to connect and list the backlog. - *"Are any humans joining the team? (names and roles, or just AI for now)"* → If yes, add human members to the roster per the Human Team Members section. - - *"Want to include the Copilot coding agent? It can pick up issues autonomously — bug fixes, tests, small features. Say 'add copilot agent' anytime to add it. (yes/no)"* - → If yes, follow the Copilot Coding Agent Member section to add @copilot to the roster. - → Also ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/always for good-fit issues/no)"* - → Generate the default capability profile (good fit / needs review / not suitable) and let the user customize. + - *"Want to include the Copilot coding agent? Run `npx github:bradygaster/squad copilot` to add it — it picks up issues autonomously."* + → This is informational — the CLI handles the actual setup. - These are additive. The user can answer all, some, or skip entirely. Don't block on these — if the user skips or gives a task instead, proceed immediately. - **PRD provided?** → Run the PRD Mode intake flow: spawn Lead to decompose, present work items. - **GitHub repo provided?** → Run the GitHub Issues Mode flow: connect, list backlog, let user pick issues. - **Humans added?** → Already in roster. Confirm: *"šŸ‘¤ {Name} is on the team as {Role}. I'll tag them when their input is needed."* - - **@copilot enabled?** → Already in roster with capability profile. Confirm: *"šŸ¤– @copilot is on the team. It'll pick up issues that match its capability profile."* + - **@copilot on roster?** → Already in roster with capability profile. Confirm: *"šŸ¤– @copilot is on the team. It'll pick up issues that match its capability profile."* --- @@ -212,9 +202,9 @@ The routing table determines **WHO** handles work. After routing, use Response M |--------|--------| | Names someone ("Ripley, fix the button") | Spawn that agent | | "Team" or multi-domain question | Spawn 2-3+ relevant agents in parallel, synthesize | -| Coding agent management ("add copilot agent", "remove copilot agent") | Follow Copilot Coding Agent Member (see that section) — this is the GitHub coding agent, NOT a new cast member | +| Coding agent — adding/removing | Inform user to use CLI: `npx github:bradygaster/squad copilot` (do NOT create a new cast member) | | Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | -| Issue suitable for @copilot ("this looks like a @copilot task", "@copilot could handle this") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | +| Issue suitable for @copilot ("this looks like a copilot task") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | | Ceremony request ("design meeting", "run a retro") | Run the matching ceremony from `ceremonies.md` (see Ceremonies) | | Issues/backlog request ("pull issues", "show backlog", "work on #N") | Follow GitHub Issues Mode (see that section) | | PRD intake ("here's the PRD", "read the PRD at X", pastes spec) | Follow PRD Mode (see that section) | @@ -1508,16 +1498,24 @@ Example roster with mixed team: The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous team member. Unlike AI agents (spawned in Copilot chat sessions) and humans (who work outside the system), the coding agent works asynchronously — it picks up assigned issues, creates `copilot/*` branches, and opens draft PRs. -### Triggers +### Adding @copilot — Use the CLI -| User says | Action | -|-----------|--------| -| "add copilot agent" / "add copilot agent to the team" | Add @copilot to roster with capability profile | -| "remove copilot agent" / "remove copilot agent from the team" | Remove @copilot from roster | -| "copilot agent capabilities" / "update copilot agent capabilities" | Edit the capability profile in team.md | -| "copilot agent auto-assign" / "turn on copilot agent auto-assign" | Set `` in team.md | -| "stop copilot agent auto-assign" | Set `` in team.md | -| "copilot agent can handle this" / "route to copilot agent" | Route current issue to @copilot | +The coding agent is managed via the CLI, not chat commands: + +```bash +# Add @copilot to the team +npx github:bradygaster/squad copilot + +# Add with auto-assign enabled +npx github:bradygaster/squad copilot --auto-assign + +# Remove @copilot from the team +npx github:bradygaster/squad copilot --off +``` + +The CLI modifies `team.md` directly and copies `copilot-instructions.md`. After running, the coordinator sees @copilot on the roster and includes it in triage/routing. + +Users can also manually edit `team.md` to add/modify/remove the @copilot entry or its capability profile. ### How the Coding Agent Differs @@ -1532,9 +1530,9 @@ The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous | **Work style** | Synchronous in session | Asynchronous (human pace) | Asynchronous (creates branch + PR) | | **Scope** | Full domain per charter | Role-based | Capability profile (three tiers) | -### Adding @copilot to the Team +### @copilot Roster Format -1. Add to `.ai-team/team.md` roster under the **Coding Agent** section: +When `npx github:bradygaster/squad copilot` is run, the CLI adds this to `team.md`: ```markdown @@ -1550,15 +1548,7 @@ The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous šŸ”“ Not suitable: Architecture decisions, multi-system design, ambiguous requirements, security-critical changes ``` -2. Add routing entries to `.ai-team/routing.md`: - -```markdown -| Bug fixes, test coverage, lint fixes | @copilot šŸ¤– | Small, well-defined tasks with clear acceptance criteria | -``` - -3. Ensure `.github/copilot-instructions.md` exists (created during `squad init` if @copilot is enabled, or copy from `.ai-team-templates/copilot-instructions.md`). - -4. Announce: `"šŸ¤– @copilot joined the team as Coding Agent. I'll route suitable issues to it based on the capability profile."` +The CLI also adds routing entries to `.ai-team/routing.md` and copies `.github/copilot-instructions.md`. ### Capability Profile diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 40ac71896..d30df4ff3 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -6,38 +6,27 @@ Add the GitHub Copilot coding agent to your Squad as an autonomous team member. ## Enabling @copilot -### New projects (during init) +### Add to an existing Squad -Squad asks during team setup: +```bash +# Add @copilot to the team +npx github:bradygaster/squad copilot -``` -> Want to include the Copilot coding agent (@copilot)? -> It can pick up issues autonomously — bug fixes, tests, small features. +# Add with auto-assign enabled +npx github:bradygaster/squad copilot --auto-assign ``` -Say **yes**, and @copilot is added to the roster with a default capability profile. +This adds @copilot to your roster with a default capability profile and creates `.github/copilot-instructions.md`. -### Existing projects (after upgrade) - -1. Run the upgrade to get the latest Squad: +### Remove from the team ```bash -npx github:bradygaster/squad upgrade +npx github:bradygaster/squad copilot --off ``` -2. Open a Copilot session with Squad and say: - -``` -> add copilot agent -``` +### During init -3. Copy the instructions template so the coding agent knows about your Squad: - -```bash -cp .ai-team-templates/copilot-instructions.md .github/copilot-instructions.md -``` - -Squad will set up the roster entry, capability profile, and routing. +Squad mentions the option during team setup. Run the `copilot` subcommand after init to add it. --- @@ -78,16 +67,11 @@ The profile is editable. The Lead can suggest updates based on experience: When enabled, the `squad-issue-assign` workflow automatically assigns `@copilot` on the GitHub issue when work is routed to it — so the coding agent picks it up without manual intervention. Enable it: -``` -> copilot agent auto-assign -``` - -Disable it: -``` -> stop copilot agent auto-assign +```bash +npx github:bradygaster/squad copilot --auto-assign ``` -The setting is stored in `team.md` as ``. +Or set it manually in `team.md` by changing `` to ``. --- diff --git a/index.js b/index.js index c0890bc88..df5385722 100644 --- a/index.js +++ b/index.js @@ -37,6 +37,8 @@ if (cmd === '--help' || cmd === '-h' || cmd === 'help') { console.log(` ${BOLD}upgrade${RESET} Update Squad-owned files to latest version`); console.log(` Overwrites: squad.agent.md, .ai-team-templates/`); console.log(` Never touches: .ai-team/ (your team state)`); + console.log(` ${BOLD}copilot${RESET} Add/remove the Copilot coding agent (@copilot)`); + console.log(` Usage: copilot [--off] [--auto-assign]`); console.log(` ${BOLD}export${RESET} Export squad to a portable JSON snapshot`); console.log(` Default: squad-export.json (use --out to override)`); console.log(` ${BOLD}import${RESET} Import squad from an export file`); @@ -64,6 +66,120 @@ function copyRecursive(src, target) { } } +// --- Copilot subcommand --- +if (cmd === 'copilot') { + const teamMd = path.join(dest, '.ai-team', 'team.md'); + if (!fs.existsSync(teamMd)) { + fatal('No squad found — run init first, then add the copilot agent.'); + } + + const isOff = process.argv.includes('--off'); + const autoAssign = process.argv.includes('--auto-assign'); + let content = fs.readFileSync(teamMd, 'utf8'); + const hasCopilot = content.includes('šŸ¤– Coding Agent'); + + if (isOff) { + if (!hasCopilot) { + console.log(`${DIM}Copilot coding agent is not on the team — nothing to remove${RESET}`); + process.exit(0); + } + // Remove the Coding Agent section + content = content.replace(/\n## Coding Agent\n[\s\S]*?(?=\n## |\n*$)/, ''); + fs.writeFileSync(teamMd, content); + console.log(`${GREEN}āœ“${RESET} Removed @copilot from the team roster`); + + // Remove copilot-instructions.md + const instructionsDest = path.join(dest, '.github', 'copilot-instructions.md'); + if (fs.existsSync(instructionsDest)) { + fs.unlinkSync(instructionsDest); + console.log(`${GREEN}āœ“${RESET} Removed .github/copilot-instructions.md`); + } + process.exit(0); + } + + // Adding copilot + if (hasCopilot) { + // Update auto-assign if requested + if (autoAssign) { + content = content.replace('', ''); + fs.writeFileSync(teamMd, content); + console.log(`${GREEN}āœ“${RESET} Enabled @copilot auto-assign`); + } else { + console.log(`${DIM}@copilot is already on the team${RESET}`); + } + process.exit(0); + } + + // Add Coding Agent section before Project Context + const autoAssignValue = autoAssign ? 'true' : 'false'; + const copilotSection = ` +## Coding Agent + + + +| Name | Role | Charter | Status | +|------|------|---------|--------| +| @copilot | Coding Agent | — | šŸ¤– Coding Agent | + +### Capabilities + +**🟢 Good fit — auto-route when enabled:** +- Bug fixes with clear reproduction steps +- Test coverage (adding missing tests, fixing flaky tests) +- Lint/format fixes and code style cleanup +- Dependency updates and version bumps +- Small isolated features with clear specs +- Boilerplate/scaffolding generation +- Documentation fixes and README updates + +**🟔 Needs review — route to @copilot but flag for squad member PR review:** +- Medium features with clear specs and acceptance criteria +- Refactoring with existing test coverage +- API endpoint additions following established patterns +- Migration scripts with well-defined schemas + +**šŸ”“ Not suitable — route to squad member instead:** +- Architecture decisions and system design +- Multi-system integration requiring coordination +- Ambiguous requirements needing clarification +- Security-critical changes (auth, encryption, access control) +- Performance-critical paths requiring benchmarking +- Changes requiring cross-team discussion + +`; + + // Insert before "## Project Context" if it exists, otherwise append + if (content.includes('## Project Context')) { + content = content.replace('## Project Context', copilotSection + '## Project Context'); + } else { + content = content.trimEnd() + '\n' + copilotSection; + } + + fs.writeFileSync(teamMd, content); + console.log(`${GREEN}āœ“${RESET} Added @copilot (Coding Agent) to team roster`); + if (autoAssign) { + console.log(`${GREEN}āœ“${RESET} Auto-assign enabled — squad-labeled issues will be assigned to @copilot`); + } + + // Copy copilot-instructions.md + const instructionsSrc = path.join(root, 'templates', 'copilot-instructions.md'); + const instructionsDest = path.join(dest, '.github', 'copilot-instructions.md'); + if (fs.existsSync(instructionsSrc)) { + fs.mkdirSync(path.dirname(instructionsDest), { recursive: true }); + fs.copyFileSync(instructionsSrc, instructionsDest); + console.log(`${GREEN}āœ“${RESET} .github/copilot-instructions.md`); + } + + console.log(); + console.log(`${BOLD}@copilot is on the team.${RESET}`); + console.log(`The coding agent will pick up issues matching its capability profile.`); + if (!autoAssign) { + console.log(`Run with ${BOLD}--auto-assign${RESET} to auto-assign @copilot on squad-labeled issues.`); + } + console.log(); + process.exit(0); +} + // --- Export subcommand --- if (cmd === 'export') { const teamMd = path.join(dest, '.ai-team', 'team.md'); @@ -488,22 +604,11 @@ if (!fs.existsSync(ceremoniesDest)) { console.log(`${DIM}ceremonies.md already exists — skipping${RESET}`); } -// Copy copilot-instructions.md (user-owned on init, upgraded if @copilot is enabled) +// copilot-instructions.md — managed by `squad copilot` subcommand +// On upgrade, update if @copilot is enabled on the team const copilotInstructionsSrc = path.join(root, 'templates', 'copilot-instructions.md'); const copilotInstructionsDest = path.join(dest, '.github', 'copilot-instructions.md'); - -if (!isUpgrade) { - if (fs.existsSync(copilotInstructionsSrc)) { - if (!fs.existsSync(copilotInstructionsDest)) { - fs.mkdirSync(path.dirname(copilotInstructionsDest), { recursive: true }); - fs.copyFileSync(copilotInstructionsSrc, copilotInstructionsDest); - console.log(`${GREEN}āœ“${RESET} .github/copilot-instructions.md`); - } else { - console.log(`${DIM}copilot-instructions.md already exists — skipping${RESET}`); - } - } -} else { - // On upgrade, update copilot-instructions.md if @copilot is enabled on the team +if (isUpgrade) { const teamMd = path.join(dest, '.ai-team', 'team.md'); const copilotEnabled = fs.existsSync(teamMd) && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); @@ -592,7 +697,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Say ${BOLD}"add copilot agent"${RESET} in your next Squad session to add it to your team.`); + console.log(` Run ${BOLD}npx github:bradygaster/squad copilot${RESET} to add it to your team.`); } } diff --git a/test/index.test.js b/test/index.test.js index 8ec280fb5..48812a8a6 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -544,9 +544,18 @@ describe('edge cases', () => { }); }); -describe('copilot-instructions.md', () => { +describe('copilot subcommand', () => { let tmpDir; + function initWithTeam(dir) { + runInit(dir); + // Init creates directories but not team.md (coordinator does that) + // Create a minimal team.md so the copilot subcommand can proceed + const teamDir = path.join(dir, '.ai-team'); + fs.mkdirSync(teamDir, { recursive: true }); + fs.writeFileSync(path.join(teamDir, 'team.md'), '# The Squad\n\n## Project Context\n\nNo context yet.\n'); + } + beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-copilot-')); }); @@ -555,29 +564,73 @@ describe('copilot-instructions.md', () => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); - it('creates .github/copilot-instructions.md on init', () => { - runInit(tmpDir); + it('fails when no squad exists', () => { + const { status } = runCmdStatus(tmpDir, 'copilot'); + assert.notEqual(status, 0, 'should fail without a squad'); + }); + + it('adds @copilot to team.md with capability profile', () => { + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot'); + const teamMd = fs.readFileSync(path.join(tmpDir, '.ai-team', 'team.md'), 'utf8'); + assert.ok(teamMd.includes('šŸ¤– Coding Agent'), 'should have coding agent badge'); + assert.ok(teamMd.includes('@copilot'), 'should have @copilot name'); + assert.ok(teamMd.includes('🟢 Good fit'), 'should have capability profile'); + assert.ok(teamMd.includes('🟔 Needs review'), 'should have needs-review tier'); + assert.ok(teamMd.includes('šŸ”“ Not suitable'), 'should have not-suitable tier'); + }); + + it('creates .github/copilot-instructions.md', () => { + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot'); const dest = path.join(tmpDir, '.github', 'copilot-instructions.md'); assert.ok(fs.existsSync(dest), 'copilot-instructions.md should be created'); const content = fs.readFileSync(dest, 'utf8'); assert.ok(content.includes('Squad'), 'should reference Squad'); assert.ok(content.includes('.ai-team/team.md'), 'should reference team.md'); - assert.ok(content.includes('Capability Self-Check'), 'should include capability self-check'); }); - it('skips copilot-instructions.md when it already exists', () => { - // Create the file first - fs.mkdirSync(path.join(tmpDir, '.github'), { recursive: true }); - fs.writeFileSync(path.join(tmpDir, '.github', 'copilot-instructions.md'), 'custom content'); - runInit(tmpDir); - const content = fs.readFileSync(path.join(tmpDir, '.github', 'copilot-instructions.md'), 'utf8'); - assert.equal(content, 'custom content', 'should not overwrite existing file'); + it('sets auto-assign when --auto-assign flag is used', () => { + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot --auto-assign'); + const teamMd = fs.readFileSync(path.join(tmpDir, '.ai-team', 'team.md'), 'utf8'); + assert.ok(teamMd.includes(''), 'should have auto-assign enabled'); + }); + + it('defaults auto-assign to false without flag', () => { + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot'); + const teamMd = fs.readFileSync(path.join(tmpDir, '.ai-team', 'team.md'), 'utf8'); + assert.ok(teamMd.includes(''), 'should have auto-assign disabled'); + }); + + it('reports already on team if run twice', () => { + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot'); + const out = runCmd(tmpDir, 'copilot'); + assert.ok(out.includes('already on the team'), 'should note already added'); + }); + + it('enables auto-assign on existing copilot member', () => { + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot'); + runCmd(tmpDir, 'copilot --auto-assign'); + const teamMd = fs.readFileSync(path.join(tmpDir, '.ai-team', 'team.md'), 'utf8'); + assert.ok(teamMd.includes(''), 'should update to auto-assign'); + }); + + it('removes @copilot with --off flag', () => { + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot'); + runCmd(tmpDir, 'copilot --off'); + const teamMd = fs.readFileSync(path.join(tmpDir, '.ai-team', 'team.md'), 'utf8'); + assert.ok(!teamMd.includes('šŸ¤– Coding Agent'), 'should remove coding agent'); }); it('does not overwrite copilot-instructions.md on upgrade when @copilot is not enabled', () => { - runInit(tmpDir); - // Customize the file + initWithTeam(tmpDir); const dest = path.join(tmpDir, '.github', 'copilot-instructions.md'); + fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, 'user customized'); runCmd(tmpDir, 'upgrade'); const content = fs.readFileSync(dest, 'utf8'); @@ -585,12 +638,8 @@ describe('copilot-instructions.md', () => { }); it('upgrades copilot-instructions.md when @copilot is enabled on the team', () => { - runInit(tmpDir); - // Simulate @copilot being enabled in team.md - const teamDir = path.join(tmpDir, '.ai-team'); - fs.mkdirSync(teamDir, { recursive: true }); - fs.writeFileSync(path.join(teamDir, 'team.md'), '| @copilot | Coding Agent | — | šŸ¤– Coding Agent |'); - // Customize the instructions file + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot'); const dest = path.join(tmpDir, '.github', 'copilot-instructions.md'); fs.writeFileSync(dest, 'old version'); runCmd(tmpDir, 'upgrade'); @@ -600,11 +649,12 @@ describe('copilot-instructions.md', () => { }); it('copilot-instructions.md content matches source template', () => { - runInit(tmpDir); + initWithTeam(tmpDir); + runCmd(tmpDir, 'copilot'); const dest = path.join(tmpDir, '.github', 'copilot-instructions.md'); const src = path.join(ROOT, 'templates', 'copilot-instructions.md'); const destContent = fs.readFileSync(dest, 'utf8'); const srcContent = fs.readFileSync(src, 'utf8'); - assert.equal(destContent, srcContent, 'init should copy template exactly'); + assert.equal(destContent, srcContent, 'should copy template exactly'); }); }); From f51df10d59ddd10ec2388ce7b190cc1104e05c07 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 21:05:55 -0500 Subject: [PATCH 08/33] feat: add conversational trigger 'add team member copilot' - Primary flow: coordinator handles in conversation during init or team mode - Routing signal: 'add team member copilot' maps to Copilot Coding Agent Member section - CLI subcommand kept as backup option - Updated docs to show conversation-first, CLI-second approach --- .github/agents/squad.agent.md | 34 ++++++++++----------------- docs/features/copilot-coding-agent.md | 25 +++++++++++--------- index.js | 2 +- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 5c2466c60..2d1da39a7 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -22,10 +22,6 @@ Check: Does `.ai-team/team.md` exist? - **No** → Init Mode - **Yes** → Team Mode -### Adding the Copilot Coding Agent - -The coding agent is added via the CLI: `npx github:bradygaster/squad copilot`. Once added, the Copilot Coding Agent Member section describes how it works with the team. - --- ## Init Mode @@ -102,8 +98,9 @@ The `union` merge driver keeps all lines from both sides, which is correct for a → If yes, follow the GitHub Issues Mode flow to connect and list the backlog. - *"Are any humans joining the team? (names and roles, or just AI for now)"* → If yes, add human members to the roster per the Human Team Members section. - - *"Want to include the Copilot coding agent? Run `npx github:bradygaster/squad copilot` to add it — it picks up issues autonomously."* - → This is informational — the CLI handles the actual setup. + - *"Want to include the Copilot coding agent (@copilot)? It can pick up issues autonomously — bug fixes, tests, small features. (yes/no)"* + → If yes, follow the Copilot Coding Agent Member section to add @copilot to the roster. + → Also ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/no)"* - These are additive. The user can answer all, some, or skip entirely. Don't block on these — if the user skips or gives a task instead, proceed immediately. - **PRD provided?** → Run the PRD Mode intake flow: spawn Lead to decompose, present work items. - **GitHub repo provided?** → Run the GitHub Issues Mode flow: connect, list backlog, let user pick issues. @@ -202,7 +199,7 @@ The routing table determines **WHO** handles work. After routing, use Response M |--------|--------| | Names someone ("Ripley, fix the button") | Spawn that agent | | "Team" or multi-domain question | Spawn 2-3+ relevant agents in parallel, synthesize | -| Coding agent — adding/removing | Inform user to use CLI: `npx github:bradygaster/squad copilot` (do NOT create a new cast member) | +| "add team member copilot" | Add @copilot to the roster per the Copilot Coding Agent Member section — this is NOT a new cast member, it is the GitHub Copilot coding agent | | Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | | Issue suitable for @copilot ("this looks like a copilot task") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | | Ceremony request ("design meeting", "run a retro") | Run the matching ceremony from `ceremonies.md` (see Ceremonies) | @@ -1498,24 +1495,17 @@ Example roster with mixed team: The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous team member. Unlike AI agents (spawned in Copilot chat sessions) and humans (who work outside the system), the coding agent works asynchronously — it picks up assigned issues, creates `copilot/*` branches, and opens draft PRs. -### Adding @copilot — Use the CLI - -The coding agent is managed via the CLI, not chat commands: +### Adding @copilot — "add team member copilot" -```bash -# Add @copilot to the team -npx github:bradygaster/squad copilot - -# Add with auto-assign enabled -npx github:bradygaster/squad copilot --auto-assign - -# Remove @copilot from the team -npx github:bradygaster/squad copilot --off -``` +When the user says **"add team member copilot"** (or similar — e.g., "add copilot to the team", "I want copilot on the squad"), add @copilot to the roster. This is NOT a new cast member — do NOT cast a name from the universe. @copilot is always named "@copilot". -The CLI modifies `team.md` directly and copies `copilot-instructions.md`. After running, the coordinator sees @copilot on the roster and includes it in triage/routing. +**Steps:** +1. Add the Coding Agent section to `team.md` (see @copilot Roster Format below) +2. Ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/no)"* +3. Set `` based on the answer +4. Announce: *"šŸ¤– @copilot joined the team as Coding Agent. I'll route suitable issues to it based on the capability profile."* -Users can also manually edit `team.md` to add/modify/remove the @copilot entry or its capability profile. +The user can also add @copilot via the CLI: `npx github:bradygaster/squad copilot` ### How the Coding Agent Differs diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index d30df4ff3..23ec73118 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -6,7 +6,19 @@ Add the GitHub Copilot coding agent to your Squad as an autonomous team member. ## Enabling @copilot -### Add to an existing Squad +### In conversation (recommended) + +During team setup, Squad asks if you want to include the coding agent. Say **yes**. + +For existing teams, say: + +``` +> add team member copilot +``` + +Squad will add @copilot to the roster with a capability profile and ask about auto-assign. + +### Via CLI ```bash # Add @copilot to the team @@ -14,20 +26,11 @@ npx github:bradygaster/squad copilot # Add with auto-assign enabled npx github:bradygaster/squad copilot --auto-assign -``` - -This adds @copilot to your roster with a default capability profile and creates `.github/copilot-instructions.md`. - -### Remove from the team -```bash +# Remove from the team npx github:bradygaster/squad copilot --off ``` -### During init - -Squad mentions the option during team setup. Run the `copilot` subcommand after init to add it. - --- ## How @copilot Differs from Other Members diff --git a/index.js b/index.js index df5385722..4eb39ee40 100644 --- a/index.js +++ b/index.js @@ -697,7 +697,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Run ${BOLD}npx github:bradygaster/squad copilot${RESET} to add it to your team.`); + console.log(` Say ${BOLD}"add team member copilot"${RESET} in your next Squad session, or run ${BOLD}npx squad copilot${RESET}.`); } } From bd25936e36d8177ff2561b771bf12c73078ab946 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 21:10:35 -0500 Subject: [PATCH 09/33] fix: add copilot guards in casting and human triggers - Add @copilot exemption in Name Allocation (like Scribe) - Add 'add team member copilot' guard in Human Team Members triggers - Both redirect to Copilot Coding Agent Member section - Prevents coordinator from casting copilot as a universe character --- .github/agents/squad.agent.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 2d1da39a7..5e5eddaf4 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -1024,7 +1024,8 @@ After selecting a universe: 1. Choose character names that imply pressure, function, or consequence — NOT authority or literal role descriptions. 2. Each agent gets a unique name. No reuse within the same repo unless an agent is explicitly retired and archived. 3. **Scribe is always "Scribe"** — exempt from casting. -4. Store the mapping in `.ai-team/casting/registry.json`. +4. **@copilot is always "@copilot"** — exempt from casting. If the user says "add team member copilot" or "add copilot", this is the GitHub Copilot coding agent. Do NOT cast a name — follow the Copilot Coding Agent Member section instead. +5. Store the mapping in `.ai-team/casting/registry.json`. 5. Record the assignment snapshot in `.ai-team/casting/history.json`. 6. Use the allocated name everywhere: charter.md, history.md, team.md, routing.md, spawn prompts. @@ -1412,6 +1413,7 @@ Humans can join the Squad roster alongside AI agents. They appear in routing, ca | User says | Action | |-----------|--------| | "add {Name} as {role}" / "{Name} is our {role}" | Add human to roster | +| **"add team member copilot"** / **"add copilot"** | **STOP — this is NOT a human or cast member.** Follow the Copilot Coding Agent Member section. @copilot is the GitHub Copilot coding agent. | | "I'm on the team as {role}" / "I'm the {role}" | Add current user as human member | | "{Name} is done" / "here's what {Name} decided" | Unblock items waiting on that human | | "remove {Name}" / "{Name} is leaving the team" | Move to alumni (same as AI agents) | From 3795700a535d37b82130f0ac293eea61e9fac642 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 21:13:51 -0500 Subject: [PATCH 10/33] fix: settle on init + CLI for adding @copilot - Remove unreliable mid-session chat triggers (7 failed attempts) - Keep init-time question (conversational, works in setup flow) - Keep CLI subcommand for existing teams - Keep casting exemption for @copilot (like Scribe) - Clean up docs to reflect actual working flows --- .github/agents/squad.agent.md | 22 +++++++++++----------- docs/features/copilot-coding-agent.md | 14 +++----------- index.js | 2 +- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 5e5eddaf4..1fd5b709b 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -199,9 +199,8 @@ The routing table determines **WHO** handles work. After routing, use Response M |--------|--------| | Names someone ("Ripley, fix the button") | Spawn that agent | | "Team" or multi-domain question | Spawn 2-3+ relevant agents in parallel, synthesize | -| "add team member copilot" | Add @copilot to the roster per the Copilot Coding Agent Member section — this is NOT a new cast member, it is the GitHub Copilot coding agent | | Human member management ("add Brady as PM", routes to human) | Follow Human Team Members (see that section) | -| Issue suitable for @copilot ("this looks like a copilot task") | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | +| Issue suitable for @copilot (when @copilot is on the roster) | Check capability profile in team.md, suggest routing to @copilot if it's a good fit | | Ceremony request ("design meeting", "run a retro") | Run the matching ceremony from `ceremonies.md` (see Ceremonies) | | Issues/backlog request ("pull issues", "show backlog", "work on #N") | Follow GitHub Issues Mode (see that section) | | PRD intake ("here's the PRD", "read the PRD at X", pastes spec) | Follow PRD Mode (see that section) | @@ -1413,7 +1412,6 @@ Humans can join the Squad roster alongside AI agents. They appear in routing, ca | User says | Action | |-----------|--------| | "add {Name} as {role}" / "{Name} is our {role}" | Add human to roster | -| **"add team member copilot"** / **"add copilot"** | **STOP — this is NOT a human or cast member.** Follow the Copilot Coding Agent Member section. @copilot is the GitHub Copilot coding agent. | | "I'm on the team as {role}" / "I'm the {role}" | Add current user as human member | | "{Name} is done" / "here's what {Name} decided" | Unblock items waiting on that human | | "remove {Name}" / "{Name} is leaving the team" | Move to alumni (same as AI agents) | @@ -1497,17 +1495,19 @@ Example roster with mixed team: The GitHub Copilot coding agent (`@copilot`) can join the Squad as an autonomous team member. Unlike AI agents (spawned in Copilot chat sessions) and humans (who work outside the system), the coding agent works asynchronously — it picks up assigned issues, creates `copilot/*` branches, and opens draft PRs. -### Adding @copilot — "add team member copilot" +### Adding @copilot -When the user says **"add team member copilot"** (or similar — e.g., "add copilot to the team", "I want copilot on the squad"), add @copilot to the roster. This is NOT a new cast member — do NOT cast a name from the universe. @copilot is always named "@copilot". +@copilot can be added two ways: -**Steps:** -1. Add the Coding Agent section to `team.md` (see @copilot Roster Format below) -2. Ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/no)"* -3. Set `` based on the answer -4. Announce: *"šŸ¤– @copilot joined the team as Coding Agent. I'll route suitable issues to it based on the capability profile."* +1. **During init** — the coordinator asks "Want to include the Copilot coding agent?" as part of team setup. If yes: + - Add the Coding Agent section to `team.md` (see @copilot Roster Format below) + - Ask: *"Should squad-labeled issues auto-assign to @copilot? (yes/no)"* + - Set `` based on the answer + - Announce: *"šŸ¤– @copilot joined the team as Coding Agent. I'll route suitable issues to it based on the capability profile."* -The user can also add @copilot via the CLI: `npx github:bradygaster/squad copilot` +2. **Post-init via CLI** — `npx github:bradygaster/squad copilot` (or `copilot --auto-assign`) + +Once @copilot is on the roster, the coordinator includes it in triage and routing decisions. ### How the Coding Agent Differs diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 23ec73118..42067e9e0 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -6,19 +6,11 @@ Add the GitHub Copilot coding agent to your Squad as an autonomous team member. ## Enabling @copilot -### In conversation (recommended) +### During team setup (new projects) -During team setup, Squad asks if you want to include the coding agent. Say **yes**. +Squad asks if you want to include the coding agent during init. Say **yes** and it's added to the roster with a capability profile. -For existing teams, say: - -``` -> add team member copilot -``` - -Squad will add @copilot to the roster with a capability profile and ask about auto-assign. - -### Via CLI +### For existing teams (CLI) ```bash # Add @copilot to the team diff --git a/index.js b/index.js index 4eb39ee40..963b4d7f8 100644 --- a/index.js +++ b/index.js @@ -697,7 +697,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Say ${BOLD}"add team member copilot"${RESET} in your next Squad session, or run ${BOLD}npx squad copilot${RESET}.`); + console.log(` Run ${BOLD}npx squad copilot${RESET} to add it to your team, or include it during your next ${BOLD}squad init${RESET}.`); } } From fef7224ccf6d9a38877ae081597a01270ea5e782 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 21:16:27 -0500 Subject: [PATCH 11/33] feat: add 'squad add-copilot' as top-level trigger phrase - Place check at the very top of the agent file (line 21-24) - Before Team Mode, before project context loading - Exact phrase match avoids collision with project copilot code - 'squad' prefix makes it command-like, not a name to cast - Falls through to Team Mode if phrase doesn't match --- .github/agents/squad.agent.md | 4 +++- docs/features/copilot-coding-agent.md | 6 +++++- index.js | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 1fd5b709b..9cb3ba9b0 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -20,7 +20,9 @@ You are **Squad (Coordinator)** — the orchestrator for this project's AI team. Check: Does `.ai-team/team.md` exist? - **No** → Init Mode -- **Yes** → Team Mode +- **Yes** → Does the user's message contain the exact phrase **"squad add-copilot"**? + - **Yes** → This is NOT a work request. The user wants to add the GitHub Copilot coding agent to the team. Read `team.md`. If it already contains `šŸ¤– Coding Agent`, say *"šŸ¤– @copilot is already on the team."* Otherwise, add the Coding Agent section from the Copilot Coding Agent Member section to `team.md`, ask about auto-assign, and announce *"šŸ¤– @copilot joined the team as Coding Agent."* Then stop. + - **No** → Team Mode --- diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 42067e9e0..2a615f560 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -10,7 +10,11 @@ Add the GitHub Copilot coding agent to your Squad as an autonomous team member. Squad asks if you want to include the coding agent during init. Say **yes** and it's added to the roster with a capability profile. -### For existing teams (CLI) +### In conversation (existing teams) + +Say **"squad add-copilot"** in a Squad session. The coordinator will add @copilot to the roster and ask about auto-assign. + +### Via CLI ```bash # Add @copilot to the team diff --git a/index.js b/index.js index 963b4d7f8..57ee74823 100644 --- a/index.js +++ b/index.js @@ -697,7 +697,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Run ${BOLD}npx squad copilot${RESET} to add it to your team, or include it during your next ${BOLD}squad init${RESET}.`); + console.log(` Say ${BOLD}"squad add-copilot"${RESET} in a Squad session, or run ${BOLD}npx squad copilot${RESET}.`); } } From 8a58367eda6b28513fede003c92d9ce4d5fe8eff Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 10 Feb 2026 21:20:58 -0500 Subject: [PATCH 12/33] feat: use /squad-bot as trigger phrase - Slash-command syntax signals 'this is a command, not a work request' - 'bot' has zero semantic overlap with project copilot code - Placed at line 21 with explicit STOP instructions - Prevents file scanning, name casting, project analysis --- .github/agents/squad.agent.md | 4 ++-- docs/features/copilot-coding-agent.md | 2 +- index.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 9cb3ba9b0..e03be380a 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -20,8 +20,8 @@ You are **Squad (Coordinator)** — the orchestrator for this project's AI team. Check: Does `.ai-team/team.md` exist? - **No** → Init Mode -- **Yes** → Does the user's message contain the exact phrase **"squad add-copilot"**? - - **Yes** → This is NOT a work request. The user wants to add the GitHub Copilot coding agent to the team. Read `team.md`. If it already contains `šŸ¤– Coding Agent`, say *"šŸ¤– @copilot is already on the team."* Otherwise, add the Coding Agent section from the Copilot Coding Agent Member section to `team.md`, ask about auto-assign, and announce *"šŸ¤– @copilot joined the team as Coding Agent."* Then stop. +- **Yes** → Does the user's message match **"/squad-bot"** (exactly, case-insensitive)? + - **Yes** → **STOP. Do not analyze the project. Do not scan files. Do not cast a name.** This is a command to add the GitHub Copilot coding agent (@copilot) to the team roster. Read `team.md`. If it already contains `šŸ¤– Coding Agent`, respond *"šŸ¤– @copilot is already on the team."* and stop. Otherwise, add the Coding Agent section (from the Copilot Coding Agent Member section below) to `team.md`, ask *"Should squad-labeled issues auto-assign to @copilot? (yes/no)"*, write the auto-assign setting, and respond *"šŸ¤– @copilot joined the team as Coding Agent. I'll route suitable issues based on the capability profile."* Then stop. Do nothing else. - **No** → Team Mode --- diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 2a615f560..9caa736e9 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -12,7 +12,7 @@ Squad asks if you want to include the coding agent during init. Say **yes** and ### In conversation (existing teams) -Say **"squad add-copilot"** in a Squad session. The coordinator will add @copilot to the roster and ask about auto-assign. +Say **`/squad-bot`** in a Squad session. The coordinator will add @copilot to the roster and ask about auto-assign. ### Via CLI diff --git a/index.js b/index.js index 57ee74823..c30bdbb09 100644 --- a/index.js +++ b/index.js @@ -697,7 +697,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Say ${BOLD}"squad add-copilot"${RESET} in a Squad session, or run ${BOLD}npx squad copilot${RESET}.`); + console.log(` Say ${BOLD}/squad-bot${RESET} in a Squad session, or run ${BOLD}npx squad copilot${RESET}.`); } } From 22efc6f559ce75848027dfcc062a4c02d52cb65c Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 07:04:04 -0500 Subject: [PATCH 13/33] fix: always assign @copilot when squad:copilot label is applied - squad:copilot label now always assigns @copilot (no roster lookup needed) - Assignment happens immediately in the copilot branch, not conditionally - Simplified comment (removed auto-assign conditional) - Removed redundant auto-assign block --- templates/workflows/squad-issue-assign.yml | 35 ++++++++++------------ 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 01632a5d8..9c79f85f4 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -45,7 +45,21 @@ jobs: let assignedMember = null; if (isCopilotAssignment) { + // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; + + // Always assign @copilot to the issue so the coding agent picks it up + try { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + assignees: ['copilot'] + }); + core.info(`Assigned @copilot to issue #${issue.number}`); + } catch (err) { + core.warning(`Could not assign @copilot: ${err.message}`); + } } else { let inMembersTable = false; for (const line of lines) { @@ -77,34 +91,15 @@ jobs: return; } - // Auto-assign @copilot if enabled and this is a copilot assignment - if (isCopilotAssignment && copilotAutoAssign) { - try { - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: ['copilot'] - }); - core.info(`Auto-assigned @copilot to issue #${issue.number}`); - } catch (err) { - core.warning(`Could not auto-assign @copilot: ${err.message}`); - } - } - // Post assignment acknowledgment let comment; if (isCopilotAssignment) { - const autoAssignNote = copilotAutoAssign - ? `@copilot has been assigned and will pick this up automatically.` - : `Assign @copilot on this issue to start autonomous work.`; - comment = [ `### šŸ¤– Routed to @copilot (Coding Agent)`, '', `**Issue:** #${issue.number} — ${issue.title}`, '', - autoAssignNote, + `@copilot has been assigned and will pick this up automatically.`, '', `> The coding agent will create a \`copilot/*\` branch and open a draft PR.`, `> Review the PR as you would any team member's work.`, From c9d572c0f36e4d0b108b28c08737ea3fb106c327 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 07:23:48 -0500 Subject: [PATCH 14/33] fix: upgrade early-exit path now updates workflows and agent.md - 'Already up to date' path was skipping workflow and agent copies - Now always refreshes squad-owned files even when version matches - Fixes issue where re-running upgrade didn't pick up workflow changes --- index.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/index.js b/index.js index c30bdbb09..6b4eaefd5 100644 --- a/index.js +++ b/index.js @@ -544,6 +544,27 @@ if (isUpgrade) { console.log(`${GREEN}āœ“${RESET} ${BOLD}upgraded${RESET} .github/copilot-instructions.md`); } + // Always update squad-owned workflows even when version matches + const workflowsSrcEarly = path.join(root, 'templates', 'workflows'); + const workflowsDestEarly = path.join(dest, '.github', 'workflows'); + if (fs.existsSync(workflowsSrcEarly) && fs.statSync(workflowsSrcEarly).isDirectory()) { + const wfFiles = fs.readdirSync(workflowsSrcEarly).filter(f => f.endsWith('.yml')); + fs.mkdirSync(workflowsDestEarly, { recursive: true }); + for (const file of wfFiles) { + fs.copyFileSync(path.join(workflowsSrcEarly, file), path.join(workflowsDestEarly, file)); + } + console.log(`${GREEN}āœ“${RESET} ${BOLD}upgraded${RESET} squad workflows (${wfFiles.length} files)`); + } + + // Always refresh squad.agent.md (may have changed on same version via branch) + try { + fs.mkdirSync(path.dirname(agentDest), { recursive: true }); + fs.copyFileSync(agentSrc, agentDest); + stampVersion(agentDest); + } catch (err) { + // Non-fatal in early-exit path + } + console.log(`${GREEN}āœ“${RESET} Already up to date (v${pkg.version})`); process.exit(0); } From 83210a5ce4b2f12f3271831ca1eb737454346464 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 07:36:47 -0500 Subject: [PATCH 15/33] fix: use gh CLI to assign @copilot instead of REST API - REST API addAssignees doesn't work for @copilot (special entity) - Use 'gh issue edit --add-assignee @copilot' which is the documented approach - Script step sets output, follow-up step runs gh CLI conditionally --- templates/workflows/squad-issue-assign.yml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 9c79f85f4..096c07422 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -17,6 +17,7 @@ jobs: - uses: actions/checkout@v4 - name: Identify assigned member and trigger work + id: assign uses: actions/github-script@v7 with: script: | @@ -47,19 +48,7 @@ jobs: if (isCopilotAssignment) { // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; - - // Always assign @copilot to the issue so the coding agent picks it up - try { - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: ['copilot'] - }); - core.info(`Assigned @copilot to issue #${issue.number}`); - } catch (err) { - core.warning(`Could not assign @copilot: ${err.message}`); - } + core.setOutput('is_copilot', 'true'); } else { let inMembersTable = false; for (const line of lines) { @@ -126,3 +115,9 @@ jobs: }); core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); + + - name: Assign @copilot to issue + if: steps.assign.outputs.is_copilot == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: gh issue edit ${{ github.event.issue.number }} --add-assignee "@copilot" From bb4e4f446613c9bad2fea48df248187002984910 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 07:43:30 -0500 Subject: [PATCH 16/33] fix: assign @copilot inline via gh CLI in github-script - Use execSync with gh issue edit --add-assignee inside the script step - Pass GH_TOKEN via env to the step - Removes separate step that wasn't executing (output issue) --- templates/workflows/squad-issue-assign.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 096c07422..1a6718ac1 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -17,8 +17,9 @@ jobs: - uses: actions/checkout@v4 - name: Identify assigned member and trigger work - id: assign uses: actions/github-script@v7 + env: + GH_TOKEN: ${{ github.token }} with: script: | const fs = require('fs'); @@ -48,7 +49,17 @@ jobs: if (isCopilotAssignment) { // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; - core.setOutput('is_copilot', 'true'); + + // Assign @copilot using gh CLI (the documented way for the coding agent) + const { execSync } = require('child_process'); + try { + execSync(`gh issue edit ${issue.number} --add-assignee "@copilot"`, { + env: { ...process.env, GH_TOKEN: process.env.GH_TOKEN } + }); + core.info(`Assigned @copilot to issue #${issue.number}`); + } catch (err) { + core.warning(`Could not assign @copilot: ${err.message}`); + } } else { let inMembersTable = false; for (const line of lines) { @@ -115,9 +126,3 @@ jobs: }); core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); - - - name: Assign @copilot to issue - if: steps.assign.outputs.is_copilot == 'true' - env: - GH_TOKEN: ${{ github.token }} - run: gh issue edit ${{ github.event.issue.number }} --add-assignee "@copilot" From c3f97434505ee2ffdfe9a0c4e24aa36aa6ff9911 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 07:44:38 -0500 Subject: [PATCH 17/33] fix: match official gh CLI pattern for copilot assignment - Use --add-assignee copilot (no @) - Use --repo GITHUB_REPOSITORY - Use secrets.GITHUB_TOKEN per official docs --- templates/workflows/squad-issue-assign.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 1a6718ac1..c5cba5b1d 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -19,7 +19,7 @@ jobs: - name: Identify assigned member and trigger work uses: actions/github-script@v7 env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: script: | const fs = require('fs'); @@ -50,11 +50,11 @@ jobs: // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; - // Assign @copilot using gh CLI (the documented way for the coding agent) + // Assign @copilot using gh CLI const { execSync } = require('child_process'); try { - execSync(`gh issue edit ${issue.number} --add-assignee "@copilot"`, { - env: { ...process.env, GH_TOKEN: process.env.GH_TOKEN } + execSync(`gh issue edit ${issue.number} --repo ${process.env.GITHUB_REPOSITORY} --add-assignee copilot`, { + env: { ...process.env } }); core.info(`Assigned @copilot to issue #${issue.number}`); } catch (err) { From 79dde58181a2e05be53fbc21e58d49bb976e7f3c Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 07:51:18 -0500 Subject: [PATCH 18/33] fix: use REST API instead of gh CLI for @copilot assignment The gh CLI 'add-assignee copilot' fails with 'copilot not found' in workflow context. Switch to github.rest.issues.addAssignees() which properly recognizes the Copilot coding agent bot user. --- templates/workflows/squad-issue-assign.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index c5cba5b1d..22d059080 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -18,8 +18,6 @@ jobs: - name: Identify assigned member and trigger work uses: actions/github-script@v7 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: script: | const fs = require('fs'); @@ -50,11 +48,13 @@ jobs: // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; - // Assign @copilot using gh CLI - const { execSync } = require('child_process'); + // Assign @copilot via REST API try { - execSync(`gh issue edit ${issue.number} --repo ${process.env.GITHUB_REPOSITORY} --add-assignee copilot`, { - env: { ...process.env } + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + assignees: ['copilot'] }); core.info(`Assigned @copilot to issue #${issue.number}`); } catch (err) { From 00d1cf2a5ab176dced938e7213419f47dfa16ac5 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 07:56:10 -0500 Subject: [PATCH 19/33] fix: don't auto-assign @copilot from workflow, guide user instead The REST API addAssignees silently ignores 'copilot' - it returns 201 but doesn't actually assign. The coding agent can only be assigned through the GitHub UI or an authenticated user's CLI session. The workflow now posts a comment with the gh CLI command for the team lead to assign @copilot manually. --- templates/workflows/squad-issue-assign.yml | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 22d059080..d17ad40fd 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -47,19 +47,6 @@ jobs: if (isCopilotAssignment) { // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; - - // Assign @copilot via REST API - try { - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: ['copilot'] - }); - core.info(`Assigned @copilot to issue #${issue.number}`); - } catch (err) { - core.warning(`Could not assign @copilot: ${err.message}`); - } } else { let inMembersTable = false; for (const line of lines) { @@ -99,9 +86,13 @@ jobs: '', `**Issue:** #${issue.number} — ${issue.title}`, '', - `@copilot has been assigned and will pick this up automatically.`, + `To start autonomous work, assign @copilot to this issue:`, + '```', + `gh issue edit ${issue.number} --add-assignee copilot`, + '```', + `Or use the **Assignees** sidebar on the right →`, '', - `> The coding agent will create a \`copilot/*\` branch and open a draft PR.`, + `> Once assigned, the coding agent will create a \`copilot/*\` branch and open a draft PR.`, `> Review the PR as you would any team member's work.`, ].join('\n'); } else { From 2bc6aad6d1a5876dcf12b060c690589f45fb7383 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 08:07:34 -0500 Subject: [PATCH 20/33] fix: use copilot-swe-agent[bot] with agent_assignment API for auto-assign Per GitHub docs, the coding agent assignee is 'copilot-swe-agent[bot]' (not 'copilot') and requires the agent_assignment payload in the REST API call. Uses SQUAD_TOKEN (PAT) if available, falls back to GITHUB_TOKEN. --- templates/workflows/squad-issue-assign.yml | 44 +++++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index d17ad40fd..04ffa2885 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -18,6 +18,9 @@ jobs: - name: Identify assigned member and trigger work uses: actions/github-script@v7 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SQUAD_TOKEN: ${{ secrets.SQUAD_TOKEN }} with: script: | const fs = require('fs'); @@ -47,6 +50,39 @@ jobs: if (isCopilotAssignment) { // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; + + // Assign coding agent via REST API with agent_assignment payload + try { + const response = await fetch( + `https://api.github.com/repos/${context.repo.owner}/${context.repo.repo}/issues/${issue.number}/assignees`, + { + method: 'POST', + headers: { + 'Accept': 'application/vnd.github+json', + 'Authorization': `Bearer ${process.env.SQUAD_TOKEN || process.env.GITHUB_TOKEN}`, + 'X-GitHub-Api-Version': '2022-11-28' + }, + body: JSON.stringify({ + assignees: ['copilot-swe-agent[bot]'], + agent_assignment: { + target_repo: `${context.repo.owner}/${context.repo.repo}`, + base_branch: 'main', + custom_instructions: '', + custom_agent: '', + model: '' + } + }) + } + ); + if (!response.ok) { + const body = await response.text(); + core.warning(`Could not assign @copilot (${response.status}): ${body}`); + } else { + core.info(`Assigned @copilot to issue #${issue.number}`); + } + } catch (err) { + core.warning(`Could not assign @copilot: ${err.message}`); + } } else { let inMembersTable = false; for (const line of lines) { @@ -86,13 +122,9 @@ jobs: '', `**Issue:** #${issue.number} — ${issue.title}`, '', - `To start autonomous work, assign @copilot to this issue:`, - '```', - `gh issue edit ${issue.number} --add-assignee copilot`, - '```', - `Or use the **Assignees** sidebar on the right →`, + `@copilot has been assigned and will pick this up automatically.`, '', - `> Once assigned, the coding agent will create a \`copilot/*\` branch and open a draft PR.`, + `> The coding agent will create a \`copilot/*\` branch and open a draft PR.`, `> Review the PR as you would any team member's work.`, ].join('\n'); } else { From eab2e337dbb8e4bc316e208c7435df21e7501252 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 08:10:20 -0500 Subject: [PATCH 21/33] fix: use github.request() with COPILOT_ASSIGN_TOKEN PAT for assignment Match the exact pattern from GitHub docs: separate step using github-token with a PAT secret (COPILOT_ASSIGN_TOKEN), calling github.request() with copilot-swe-agent[bot] assignee and agent_assignment payload. --- templates/workflows/squad-issue-assign.yml | 68 ++++++++++------------ 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 04ffa2885..5711fce45 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -19,8 +19,7 @@ jobs: - name: Identify assigned member and trigger work uses: actions/github-script@v7 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SQUAD_TOKEN: ${{ secrets.SQUAD_TOKEN }} + COPILOT_ASSIGN_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN }} with: script: | const fs = require('fs'); @@ -40,9 +39,6 @@ jobs: const content = fs.readFileSync(teamFile, 'utf8'); const lines = content.split('\n'); - // Check if @copilot auto-assign is enabled - const copilotAutoAssign = content.includes(''); - // Check if this is a coding agent assignment const isCopilotAssignment = memberName === 'copilot'; @@ -50,39 +46,6 @@ jobs: if (isCopilotAssignment) { // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; - - // Assign coding agent via REST API with agent_assignment payload - try { - const response = await fetch( - `https://api.github.com/repos/${context.repo.owner}/${context.repo.repo}/issues/${issue.number}/assignees`, - { - method: 'POST', - headers: { - 'Accept': 'application/vnd.github+json', - 'Authorization': `Bearer ${process.env.SQUAD_TOKEN || process.env.GITHUB_TOKEN}`, - 'X-GitHub-Api-Version': '2022-11-28' - }, - body: JSON.stringify({ - assignees: ['copilot-swe-agent[bot]'], - agent_assignment: { - target_repo: `${context.repo.owner}/${context.repo.repo}`, - base_branch: 'main', - custom_instructions: '', - custom_agent: '', - model: '' - } - }) - } - ); - if (!response.ok) { - const body = await response.text(); - core.warning(`Could not assign @copilot (${response.status}): ${body}`); - } else { - core.info(`Assigned @copilot to issue #${issue.number}`); - } - } catch (err) { - core.warning(`Could not assign @copilot: ${err.message}`); - } } else { let inMembersTable = false; for (const line of lines) { @@ -149,3 +112,32 @@ jobs: }); core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); + + # Separate step for copilot assignment using PAT + - name: Assign @copilot coding agent + if: github.event.label.name == 'squad:copilot' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue_number = context.payload.issue.number; + + await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { + owner, + repo, + issue_number, + assignees: ['copilot-swe-agent[bot]'], + agent_assignment: { + target_repo: `${owner}/${repo}`, + base_branch: 'main', + custom_instructions: 'Pick this up based on the issue description. Follow repo conventions; add/update tests as appropriate.', + custom_agent: '', + model: '' + }, + headers: { + accept: 'application/vnd.github+json', + 'x-github-api-version': '2022-11-28' + } + }); From 1424bffacd707fdffe3198023354103b71e38d1a Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 08:19:21 -0500 Subject: [PATCH 22/33] fix: simplify copilot assignment - use copilot-swe-agent with addAssignees No PAT needed. Use github.rest.issues.addAssignees with 'copilot-swe-agent' (no [bot] suffix) and default GITHUB_TOKEN. Single step, no separate secrets required. --- templates/workflows/squad-issue-assign.yml | 40 +++++----------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 5711fce45..803c592ab 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -18,8 +18,6 @@ jobs: - name: Identify assigned member and trigger work uses: actions/github-script@v7 - env: - COPILOT_ASSIGN_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN }} with: script: | const fs = require('fs'); @@ -46,6 +44,15 @@ jobs: if (isCopilotAssignment) { // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; + + // Assign copilot-swe-agent to the issue + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + assignees: ['copilot-swe-agent'] + }); + core.info(`Assigned copilot-swe-agent to issue #${issue.number}`); } else { let inMembersTable = false; for (const line of lines) { @@ -112,32 +119,3 @@ jobs: }); core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); - - # Separate step for copilot assignment using PAT - - name: Assign @copilot coding agent - if: github.event.label.name == 'squad:copilot' - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const issue_number = context.payload.issue.number; - - await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { - owner, - repo, - issue_number, - assignees: ['copilot-swe-agent[bot]'], - agent_assignment: { - target_repo: `${owner}/${repo}`, - base_branch: 'main', - custom_instructions: 'Pick this up based on the issue description. Follow repo conventions; add/update tests as appropriate.', - custom_agent: '', - model: '' - }, - headers: { - accept: 'application/vnd.github+json', - 'x-github-api-version': '2022-11-28' - } - }); From 4a44343832acb3197bec2fce08a6b8aacd5cd83b Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:20:57 -0500 Subject: [PATCH 23/33] fix: use COPILOT_ASSIGN_TOKEN PAT for copilot-swe-agent assignment GITHUB_TOKEN (app token) silently fails to assign copilot-swe-agent. GitHub docs require a user token (PAT) for coding agent assignment. Falls back to GITHUB_TOKEN if COPILOT_ASSIGN_TOKEN not configured. --- templates/workflows/squad-issue-assign.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 803c592ab..8a981c1c6 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -19,6 +19,7 @@ jobs: - name: Identify assigned member and trigger work uses: actions/github-script@v7 with: + github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require('fs'); const issue = context.payload.issue; @@ -45,7 +46,7 @@ jobs: // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; - // Assign copilot-swe-agent to the issue + // Assign copilot-swe-agent via REST API (requires PAT — COPILOT_ASSIGN_TOKEN) await github.rest.issues.addAssignees({ owner: context.repo.owner, repo: context.repo.repo, From d7bef440b090efb67019fb32de05f19ad5c6f73c Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:30:11 -0500 Subject: [PATCH 24/33] fix: split into two steps - GITHUB_TOKEN for comments, PAT for assignment The PAT can't post comments (Resource not accessible). Use default GITHUB_TOKEN for routing/comments step, and COPILOT_ASSIGN_TOKEN PAT only for the copilot-swe-agent assignment step. --- templates/workflows/squad-issue-assign.yml | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 8a981c1c6..76a507acd 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -19,7 +19,6 @@ jobs: - name: Identify assigned member and trigger work uses: actions/github-script@v7 with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require('fs'); const issue = context.payload.issue; @@ -43,17 +42,7 @@ jobs: let assignedMember = null; if (isCopilotAssignment) { - // @copilot is the GitHub Copilot coding agent — always valid, no roster lookup needed assignedMember = { name: '@copilot', role: 'Coding Agent' }; - - // Assign copilot-swe-agent via REST API (requires PAT — COPILOT_ASSIGN_TOKEN) - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: ['copilot-swe-agent'] - }); - core.info(`Assigned copilot-swe-agent to issue #${issue.number}`); } else { let inMembersTable = false; for (const line of lines) { @@ -120,3 +109,18 @@ jobs: }); core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); + + # Separate step: assign @copilot using PAT (required for coding agent) + - name: Assign @copilot coding agent + if: github.event.label.name == 'squad:copilot' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} + script: | + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + assignees: ['copilot-swe-agent'] + }); + core.info(`Assigned copilot-swe-agent to issue #${context.payload.issue.number}`); From 9cbd61c2186f8d75a76b41e5b878fbc31e4b2606 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:35:34 -0500 Subject: [PATCH 25/33] fix: use github.request with agent_assignment payload and fallback Try copilot-swe-agent[bot] with agent_assignment payload first (per GitHub docs), fall back to copilot-swe-agent without. Wrapped in try/catch so routing comment still posts even if assignment fails. May require classic PAT with repo scope. --- templates/workflows/squad-issue-assign.yml | 43 ++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 76a507acd..664b2c91e 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -117,10 +117,39 @@ jobs: with: github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - assignees: ['copilot-swe-agent'] - }); - core.info(`Assigned copilot-swe-agent to issue #${context.payload.issue.number}`); + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue_number = context.payload.issue.number; + + // Try with agent_assignment payload (per GitHub docs) + try { + await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { + owner, + repo, + issue_number, + assignees: ['copilot-swe-agent[bot]'], + agent_assignment: { + target_repo: `${owner}/${repo}`, + base_branch: 'main', + custom_instructions: '', + custom_agent: '', + model: '' + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }); + core.info(`Assigned copilot-swe-agent to issue #${issue_number}`); + } catch (err) { + core.warning(`Assignment failed: ${err.message}`); + // Fallback: try without [bot] suffix + try { + await github.rest.issues.addAssignees({ + owner, repo, issue_number, + assignees: ['copilot-swe-agent'] + }); + core.info(`Fallback assigned copilot-swe-agent to issue #${issue_number}`); + } catch (err2) { + core.warning(`Fallback also failed: ${err2.message}. Ensure COPILOT_ASSIGN_TOKEN is a classic PAT with repo scope.`); + } + } From 57a50fab5e262a83485f6e0025b95ec50c387a72 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:44:16 -0500 Subject: [PATCH 26/33] fix: detect default branch dynamically instead of hardcoding main Slidemaker uses 'master' not 'main'. The agent_assignment base_branch was hardcoded to 'main' causing a ruleset-like error. Now fetches the repo's default_branch via API. --- templates/workflows/squad-issue-assign.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index 664b2c91e..b4970c512 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -121,7 +121,10 @@ jobs: const repo = context.repo.repo; const issue_number = context.payload.issue.number; - // Try with agent_assignment payload (per GitHub docs) + // Get the default branch name (main, master, etc.) + const { data: repoData } = await github.rest.repos.get({ owner, repo }); + const baseBranch = repoData.default_branch; + try { await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { owner, @@ -130,7 +133,7 @@ jobs: assignees: ['copilot-swe-agent[bot]'], agent_assignment: { target_repo: `${owner}/${repo}`, - base_branch: 'main', + base_branch: baseBranch, custom_instructions: '', custom_agent: '', model: '' @@ -139,10 +142,10 @@ jobs: 'X-GitHub-Api-Version': '2022-11-28' } }); - core.info(`Assigned copilot-swe-agent to issue #${issue_number}`); + core.info(`Assigned copilot-swe-agent to issue #${issue_number} (base: ${baseBranch})`); } catch (err) { - core.warning(`Assignment failed: ${err.message}`); - // Fallback: try without [bot] suffix + core.warning(`Assignment with agent_assignment failed: ${err.message}`); + // Fallback: try without agent_assignment try { await github.rest.issues.addAssignees({ owner, repo, issue_number, @@ -150,6 +153,6 @@ jobs: }); core.info(`Fallback assigned copilot-swe-agent to issue #${issue_number}`); } catch (err2) { - core.warning(`Fallback also failed: ${err2.message}. Ensure COPILOT_ASSIGN_TOKEN is a classic PAT with repo scope.`); + core.warning(`Fallback also failed: ${err2.message}`); } } From 5d3fbd147a9bbe32cdbade0d3dab95049d57fa29 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:47:20 -0500 Subject: [PATCH 27/33] docs: add COPILOT_ASSIGN_TOKEN PAT setup instructions --- docs/features/copilot-coding-agent.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 9caa736e9..43e5f3d31 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -65,7 +65,18 @@ The profile is editable. The Lead can suggest updates based on experience: When enabled, the `squad-issue-assign` workflow automatically assigns `@copilot` on the GitHub issue when work is routed to it — so the coding agent picks it up without manual intervention. -Enable it: +### Setup + +Auto-assign requires a **classic Personal Access Token** (PAT) stored as a repo secret: + +1. Create a classic PAT at https://github.com/settings/tokens/new with `repo` scope +2. Add it as a repo secret named `COPILOT_ASSIGN_TOKEN`: + ```bash + gh secret set COPILOT_ASSIGN_TOKEN --repo owner/repo + ``` + +### Enable + ```bash npx github:bradygaster/squad copilot --auto-assign ``` From 8e256acd49e3867eb8f30edd1199e6283687bfe1 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:50:56 -0500 Subject: [PATCH 28/33] feat: show COPILOT_ASSIGN_TOKEN setup instructions when enabling copilot When running 'npx squad copilot', the CLI now tells the user to create a classic PAT and set it as COPILOT_ASSIGN_TOKEN repo secret. This is required for the auto-assign workflow to work. --- index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.js b/index.js index 6b4eaefd5..20c962dec 100644 --- a/index.js +++ b/index.js @@ -177,6 +177,10 @@ if (cmd === 'copilot') { console.log(`Run with ${BOLD}--auto-assign${RESET} to auto-assign @copilot on squad-labeled issues.`); } console.log(); + console.log(`${BOLD}Required:${RESET} Add a classic PAT (repo scope) as a repo secret for auto-assignment:`); + console.log(` 1. Create token: ${DIM}https://github.com/settings/tokens/new${RESET}`); + console.log(` 2. Set secret: ${DIM}gh secret set COPILOT_ASSIGN_TOKEN${RESET}`); + console.log(); process.exit(0); } From 1e40c9cd7552e93b244fff14c8eb5c4f2dec6909 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:52:54 -0500 Subject: [PATCH 29/33] docs: rewrite copilot feature doc with clear setup guide - Added Prerequisites section with repo requirements - Added Quick Start with step-by-step commands - Added dedicated COPILOT_ASSIGN_TOKEN section explaining why classic PAT is required (fine-grained fails, GITHUB_TOKEN silently ignores) - Removed unreliable /squad-bot conversation trigger - CLI is the recommended enable path - Updated README prerequisites with PAT requirement - CLI now shows PAT setup instructions when enabling copilot --- README.md | 1 + docs/features/copilot-coding-agent.md | 84 +++++++++++++++++++-------- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b2d99ea89..ab55c36b0 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,7 @@ Squad installs three GitHub Actions workflows: - GitHub Actions must be enabled on the repository - The `GITHUB_TOKEN` needs `issues: write` and `contents: read` permissions +- For @copilot auto-assign: a classic PAT with `repo` scope stored as `COPILOT_ASSIGN_TOKEN` repo secret (see [setup guide](docs/features/copilot-coding-agent.md#copilot_assign_token-required-for-auto-assign)) - For automated issue work: [Copilot coding agent](https://docs.github.com/en/copilot) must be enabled on the repo ### Session Awareness diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 43e5f3d31..3490605ad 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -4,17 +4,39 @@ Add the GitHub Copilot coding agent to your Squad as an autonomous team member. --- -## Enabling @copilot +## Prerequisites -### During team setup (new projects) +Before enabling @copilot on your Squad, ensure: + +1. **Copilot coding agent is enabled** on the repository (Settings → Copilot → Coding agent) +2. **`copilot-setup-steps.yml`** exists in `.github/` (defines the agent's environment) +3. **GitHub Actions** are enabled on the repository + +--- + +## Quick Start + +```bash +# 1. Add @copilot to your squad with auto-assign +npx github:bradygaster/squad copilot --auto-assign -Squad asks if you want to include the coding agent during init. Say **yes** and it's added to the roster with a capability profile. +# 2. Create a classic PAT for auto-assignment (see below) +# https://github.com/settings/tokens/new → check "repo" scope -### In conversation (existing teams) +# 3. Add the PAT as a repo secret +gh secret set COPILOT_ASSIGN_TOKEN -Say **`/squad-bot`** in a Squad session. The coordinator will add @copilot to the roster and ask about auto-assign. +# 4. Commit and push +git add .github/ .ai-team/ && git commit -m "feat: add copilot to squad" && git push -### Via CLI +# 5. Test — label any issue with squad:copilot +``` + +--- + +## Enabling @copilot + +### Via CLI (recommended) ```bash # Add @copilot to the team @@ -27,6 +49,32 @@ npx github:bradygaster/squad copilot --auto-assign npx github:bradygaster/squad copilot --off ``` +### During team setup (new projects) + +Squad asks if you want to include the coding agent during `init`. Say **yes** and it's added to the roster with a default capability profile. + +--- + +## COPILOT_ASSIGN_TOKEN (required for auto-assign) + +The `squad-issue-assign` workflow needs a **classic Personal Access Token** to assign `copilot-swe-agent[bot]` to issues. The default `GITHUB_TOKEN` cannot do this. + +### Create the token + +1. Go to https://github.com/settings/tokens/new +2. **Note:** `squad-copilot-assign` +3. **Expiration:** 90 days (or your preference) +4. **Scopes:** check **`repo`** (full control of private repositories) +5. Click **Generate token** + +### Add as repo secret + +```bash +gh secret set COPILOT_ASSIGN_TOKEN --repo owner/repo +``` + +> **Why a classic PAT?** Fine-grained PATs return `403 Resource not accessible` for this endpoint. The REST API for assigning `copilot-swe-agent[bot]` requires a classic PAT with `repo` scope. The `GITHUB_TOKEN` silently ignores the assignment. + --- ## How @copilot Differs from Other Members @@ -61,27 +109,15 @@ The profile is editable. The Lead can suggest updates based on experience: --- -## Auto-Assign - -When enabled, the `squad-issue-assign` workflow automatically assigns `@copilot` on the GitHub issue when work is routed to it — so the coding agent picks it up without manual intervention. - -### Setup - -Auto-assign requires a **classic Personal Access Token** (PAT) stored as a repo secret: +## Auto-Assign Flow -1. Create a classic PAT at https://github.com/settings/tokens/new with `repo` scope -2. Add it as a repo secret named `COPILOT_ASSIGN_TOKEN`: - ```bash - gh secret set COPILOT_ASSIGN_TOKEN --repo owner/repo - ``` +When the `squad:copilot` label is added to an issue: -### Enable - -```bash -npx github:bradygaster/squad copilot --auto-assign -``` +1. **Step 1** — Workflow posts a routing comment (uses `GITHUB_TOKEN`) +2. **Step 2** — Workflow assigns `copilot-swe-agent[bot]` to the issue (uses `COPILOT_ASSIGN_TOKEN`) +3. **Step 3** — Coding agent picks up the issue, creates a `copilot/*` branch, and opens a draft PR -Or set it manually in `team.md` by changing `` to ``. +The workflow automatically detects the repo's default branch (`main`, `master`, etc.). --- From 07a099fabdc15ade673a32bc7da112d17f9300f8 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:53:41 -0500 Subject: [PATCH 30/33] =?UTF-8?q?fix:=20remove=20/squad-bot=20trigger=20?= =?UTF-8?q?=E2=80=94=20CLI=20is=20the=20only=20enable=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/agents/squad.agent.md | 4 +--- index.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index e03be380a..1fd5b709b 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -20,9 +20,7 @@ You are **Squad (Coordinator)** — the orchestrator for this project's AI team. Check: Does `.ai-team/team.md` exist? - **No** → Init Mode -- **Yes** → Does the user's message match **"/squad-bot"** (exactly, case-insensitive)? - - **Yes** → **STOP. Do not analyze the project. Do not scan files. Do not cast a name.** This is a command to add the GitHub Copilot coding agent (@copilot) to the team roster. Read `team.md`. If it already contains `šŸ¤– Coding Agent`, respond *"šŸ¤– @copilot is already on the team."* and stop. Otherwise, add the Coding Agent section (from the Copilot Coding Agent Member section below) to `team.md`, ask *"Should squad-labeled issues auto-assign to @copilot? (yes/no)"*, write the auto-assign setting, and respond *"šŸ¤– @copilot joined the team as Coding Agent. I'll route suitable issues based on the capability profile."* Then stop. Do nothing else. - - **No** → Team Mode +- **Yes** → Team Mode --- diff --git a/index.js b/index.js index 20c962dec..325050a64 100644 --- a/index.js +++ b/index.js @@ -722,7 +722,7 @@ if (isUpgrade) { && fs.readFileSync(teamMd, 'utf8').includes('šŸ¤– Coding Agent'); if (!copilotEnabled) { console.log(`\n${BOLD}New:${RESET} @copilot coding agent support is now available.`); - console.log(` Say ${BOLD}/squad-bot${RESET} in a Squad session, or run ${BOLD}npx squad copilot${RESET}.`); + console.log(` Run ${BOLD}npx squad copilot${RESET} to add it to your team.`); } } From 6650efc6950a5da40944adf7ca763c0792cd59e4 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:57:01 -0500 Subject: [PATCH 31/33] docs: add conversation trigger with caveat for copilot-named projects --- docs/features/copilot-coding-agent.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 3490605ad..f856a2a96 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -49,6 +49,12 @@ npx github:bradygaster/squad copilot --auto-assign npx github:bradygaster/squad copilot --off ``` +### In conversation + +Say something like **"I want to add copilot to the squad"** in a Squad session. The coordinator will add @copilot to the roster. + +> **Note:** If your project has features named "copilot" (e.g., a Copilot extension), the coordinator may misinterpret the phrase as project work. Use the CLI in that case. + ### During team setup (new projects) Squad asks if you want to include the coding agent during `init`. Say **yes** and it's added to the roster with a default capability profile. From 1b48e3b4a6c90c9d70750a1d2f10e621e756a121 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:58:21 -0500 Subject: [PATCH 32/33] docs: add more conversation trigger phrases --- docs/features/copilot-coding-agent.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index f856a2a96..761d490bc 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -51,7 +51,12 @@ npx github:bradygaster/squad copilot --off ### In conversation -Say something like **"I want to add copilot to the squad"** in a Squad session. The coordinator will add @copilot to the roster. +Say something like: +- **"I want to add copilot to the squad"** +- **"hire copilot to the squad"** +- **"add team member copilot"** + +The coordinator will add @copilot to the roster. > **Note:** If your project has features named "copilot" (e.g., a Copilot extension), the coordinator may misinterpret the phrase as project work. Use the CLI in that case. From 9bf411db7abb63bc153c218efa6b239c1ec6149f Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Wed, 11 Feb 2026 09:58:44 -0500 Subject: [PATCH 33/33] docs: conversation is primary enable path, CLI is fallback --- docs/features/copilot-coding-agent.md | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/features/copilot-coding-agent.md b/docs/features/copilot-coding-agent.md index 761d490bc..6c97fd1a8 100644 --- a/docs/features/copilot-coding-agent.md +++ b/docs/features/copilot-coding-agent.md @@ -36,34 +36,34 @@ git add .github/ .ai-team/ && git commit -m "feat: add copilot to squad" && git ## Enabling @copilot -### Via CLI (recommended) - -```bash -# Add @copilot to the team -npx github:bradygaster/squad copilot - -# Add with auto-assign enabled -npx github:bradygaster/squad copilot --auto-assign - -# Remove from the team -npx github:bradygaster/squad copilot --off -``` - -### In conversation +### In conversation (recommended) Say something like: - **"I want to add copilot to the squad"** - **"hire copilot to the squad"** - **"add team member copilot"** -The coordinator will add @copilot to the roster. +The coordinator will add @copilot to the roster and ask about auto-assign. -> **Note:** If your project has features named "copilot" (e.g., a Copilot extension), the coordinator may misinterpret the phrase as project work. Use the CLI in that case. +> **Note:** If your project has features named "copilot" (e.g., a Copilot extension), the coordinator may misinterpret the phrase as project work. Use the CLI fallback in that case. ### During team setup (new projects) Squad asks if you want to include the coding agent during `init`. Say **yes** and it's added to the roster with a default capability profile. +### Via CLI (fallback) + +```bash +# Add @copilot to the team +npx github:bradygaster/squad copilot + +# Add with auto-assign enabled +npx github:bradygaster/squad copilot --auto-assign + +# Remove from the team +npx github:bradygaster/squad copilot --off +``` + --- ## COPILOT_ASSIGN_TOKEN (required for auto-assign)