Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
name: "Issue Go: Clarify"
description: >
When a maintainer marks an issue go:needs-research, help scope it by asking focused
clarifying questions — but only if the maintainer has not already asked them. If the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Issue No-Go
name: "Issue Go: No"

# Deterministic Workflow: No-Go decision
# Trigger: a maintainer applies the "go:no" label to reject an issue.
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
---
name: "Issue Go: Yes"
description: >
Assign an approved issue (go:yes) to the maintainer who approved it and route it to
squad areas. Reads the prior triage analysis and the squad routing table, applies the
squad label plus matched squad:{member} labels (and squad:copilot to hand the issue
off to the Copilot coding agent), and posts an assignment rationale comment.
off to the Copilot coding agent), and posts an assignment rationale comment that,
when auto-assign is not configured (no COPILOT_ASSIGN_TOKEN), also nudges the
assignee to assign the Copilot coding agent so work can begin.

on:
issues:
Expand Down Expand Up @@ -44,6 +47,10 @@ steps:
ISSUE_TITLE: ${{ github.event.issue.title }}
GO_YES_SENDER: ${{ github.event.sender.login }}
GH_TOKEN: ${{ github.token }}
# 'true' only when the repo secret is set — i.e. squad-heartbeat can
# auto-assign the coding agent. The secret VALUE is never exposed; only
# this boolean is placed in the environment.
HAS_ASSIGN_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }}
run: |
mkdir -p /tmp/gh-aw/agent

Expand All @@ -70,6 +77,22 @@ steps:
printf '%s\n' '_No prior triage analysis comment found — route from the issue content alone._' >> "$USER_FILE"
fi

# ---------------------------------------------------------------------
# Copilot coding-agent hand-off status (deterministic, trusted context).
# NUDGE_NEEDED is true only when auto-assign is unavailable AND the coding
# agent is not already an assignee — so the agent should nudge the human.
# ---------------------------------------------------------------------
CURRENT_ASSIGNEES=$(gh issue view "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --json assignees \
--jq '[.assignees[].login] | join(",")' 2>/dev/null | tr 'A-Z' 'a-z' || printf '')
AGENT_ALREADY_ASSIGNED=false
if printf ',%s,' "$CURRENT_ASSIGNEES" | grep -qE ',(copilot-swe-agent\[bot\]|copilot-swe-agent|copilot),'; then
AGENT_ALREADY_ASSIGNED=true
fi
NUDGE_NEEDED=false
if [ "${HAS_ASSIGN_TOKEN:-false}" != "true" ] && [ "$AGENT_ALREADY_ASSIGNED" != "true" ]; then
NUDGE_NEEDED=true
fi

# ---------------------------------------------------------------------
# SYSTEM context (trusted): assignment policy + routing/team tables.
# Single-quoted printf args keep backticks/`$` literal (no expansion).
Expand All @@ -85,6 +108,11 @@ steps:
printf '%s\n' '- Apply one `squad:{member}` label for every squad area the issue touches, per the routing table below.'
printf '%s\n' '- Apply the `squad:copilot` label to hand the issue off to the Copilot coding agent to begin implementation.'
printf '%s\n' '- Never apply `go:*`, `priority:*`, `override:*`, or `type:*` labels.'
if [ "$NUDGE_NEEDED" = "true" ]; then
printf -- '- **Copilot hand-off nudge REQUIRED:** no coding agent is assigned to this issue yet. In your single assignment comment, add an `### 🤖 Action needed` section that @-mentions **@%s** and tells them to assign this issue to Copilot from the Assignees menu so implementation can begin. Do NOT mention COPILOT_ASSIGN_TOKEN, repository secrets, `.squad/team.md`, or any workflow internals in the comment.\n' "$GO_YES_SENDER"
else
printf '%s\n' '- **Copilot hand-off nudge:** NOT required — do not add any Copilot-assignment nudge to the comment (auto-assign is configured or the coding agent is already assigned).'
fi
printf '\n'
} > "$SYS_FILE"

Expand Down Expand Up @@ -226,6 +254,9 @@ context — you do not decide the assignee yourself.
`squad` label, `squad:copilot`, and the most relevant matched member labels.
6. **Comment** exactly once with `add_comment`, explaining the assignee, the matched
squad areas, and the routing evidence (which routing-table entries matched and why).
If the system policy says a **Copilot hand-off nudge** is REQUIRED, append the
`### 🤖 Action needed` section (described in that policy) to this same comment. If it
says the nudge is NOT required, omit that section entirely.

## Assignment comment format

Expand All @@ -241,6 +272,11 @@ context — you do not decide the assignee yourself.

#### Notes
<Any routing uncertainty, or "none".>

<!-- Include the section below ONLY when the system policy says a Copilot hand-off nudge is REQUIRED. Omit it otherwise. Never mention COPILOT_ASSIGN_TOKEN, secrets, or workflow internals here. -->
### 🤖 Action needed
@<assignee> — this issue is not assigned to the Copilot coding agent yet, so no agent
session has started. Please assign it to Copilot from the Assignees menu to begin work.
```

## Security Rules
Expand Down
17 changes: 12 additions & 5 deletions .github/workflows/squad-heartbeat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,19 @@ jobs:
per_page: 5
});

const unassigned = copilotIssues.filter(i =>
!i.assignees || i.assignees.length === 0
// Include issues that don't yet have the coding agent assigned.
// issue-go-yes always assigns the go:yes maintainer as owner, so an
// issue almost always has a (human) assignee — filtering on "zero
// assignees" would skip every routed issue. Filter on the agent's
// presence instead; the POST below ADDS the bot as an extra assignee
// without removing the human owner.
const COPILOT_LOGINS = new Set(['copilot-swe-agent[bot]', 'copilot-swe-agent', 'copilot']);
const needsAgent = copilotIssues.filter(i =>
!(i.assignees || []).some(a => COPILOT_LOGINS.has((a.login || '').toLowerCase()))
);

if (unassigned.length === 0) {
core.info('No unassigned squad:copilot issues');
if (needsAgent.length === 0) {
core.info('No squad:copilot issues awaiting the coding agent');
return;
}

Expand All @@ -144,7 +151,7 @@ jobs:
repo: context.repo.repo
});

for (const issue of unassigned) {
for (const issue of needsAgent) {
try {
await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', {
owner: context.repo.owner,
Expand Down
24 changes: 24 additions & 0 deletions .squad/team.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@
| SecurityExpert | 🔒 Security Expert | [charter](.squad/agents/securityexpert/charter.md) | ✅ Active |
| Ralph | 🔄 Work Monitor | — | ✅ Active |

## Coding Agent

<!-- copilot-auto-assign: true -->

GitHub's Copilot coding agent (`@copilot`) autonomously implements issues labeled
`squad:copilot`. Ralph (`squad-heartbeat.yml`) assigns `copilot-swe-agent[bot]` to
every open `squad:copilot` issue that does not already have the agent assigned —
in addition to the human maintainer who owns it — which is what actually starts an
agent session (the label alone does not).

| Name | Role | Auto-assign | Notes |
|------|------|-------------|-------|
| Copilot | 🤖 Coding Agent | ✅ Enabled | Requires the `COPILOT_ASSIGN_TOKEN` repo secret (a PAT that can assign the coding agent). Without it the assign step is a no-op. |

**Setting up `COPILOT_ASSIGN_TOKEN`:** the default `GITHUB_TOKEN` cannot assign the
coding agent, so the assign step needs a user token stored as a repo secret. Create a
[fine-grained personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
(ideally on a machine/bot account) with **Repository permissions** — *Metadata*: read,
and *Actions*, *Contents*, *Issues*, and *Pull requests*: read & write — then add it as a
[repository secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-a-repository)
named `COPILOT_ASSIGN_TOKEN`. A classic PAT with the `repo` scope also works. The exact
token requirements are documented under
[Assigning issues to Copilot via the API](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api).

## Issue Source

- **Repository:** Azure/apiops-cli
Expand Down
17 changes: 12 additions & 5 deletions .squad/templates/workflows/squad-heartbeat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,19 @@ jobs:
per_page: 5
});

const unassigned = copilotIssues.filter(i =>
!i.assignees || i.assignees.length === 0
// Include issues that don't yet have the coding agent assigned.
// issue-go-yes always assigns the go:yes maintainer as owner, so an
// issue almost always has a (human) assignee — filtering on "zero
// assignees" would skip every routed issue. Filter on the agent's
// presence instead; the POST below ADDS the bot as an extra assignee
// without removing the human owner.
const COPILOT_LOGINS = new Set(['copilot-swe-agent[bot]', 'copilot-swe-agent', 'copilot']);
const needsAgent = copilotIssues.filter(i =>
!(i.assignees || []).some(a => COPILOT_LOGINS.has((a.login || '').toLowerCase()))
);

if (unassigned.length === 0) {
core.info('No unassigned squad:copilot issues');
if (needsAgent.length === 0) {
core.info('No squad:copilot issues awaiting the coding agent');
return;
}

Expand All @@ -148,7 +155,7 @@ jobs:
repo: context.repo.repo
});

for (const issue of unassigned) {
for (const issue of needsAgent) {
try {
await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', {
owner: context.repo.owner,
Expand Down