@bradygaster/squad-sdk 0.8.17 → 0.8.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/client.d.ts.map +1 -1
- package/dist/adapter/client.js +2 -0
- package/dist/adapter/client.js.map +1 -1
- package/dist/config/init.d.ts +43 -2
- package/dist/config/init.d.ts.map +1 -1
- package/dist/config/init.js +389 -46
- package/dist/config/init.js.map +1 -1
- package/dist/index.d.ts +8 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -9
- package/dist/index.js.map +1 -1
- package/dist/ralph/index.js +5 -5
- package/dist/ralph/index.js.map +1 -1
- package/dist/remote/bridge.d.ts +2 -0
- package/dist/remote/bridge.d.ts.map +1 -1
- package/dist/remote/bridge.js +34 -4
- package/dist/remote/bridge.js.map +1 -1
- package/dist/resolution.d.ts +13 -0
- package/dist/resolution.d.ts.map +1 -1
- package/dist/resolution.js +9 -1
- package/dist/resolution.js.map +1 -1
- package/dist/sharing/consult.d.ts +226 -0
- package/dist/sharing/consult.d.ts.map +1 -0
- package/dist/sharing/consult.js +818 -0
- package/dist/sharing/consult.js.map +1 -0
- package/dist/sharing/index.d.ts +2 -1
- package/dist/sharing/index.d.ts.map +1 -1
- package/dist/sharing/index.js +2 -1
- package/dist/sharing/index.js.map +1 -1
- package/package.json +207 -205
- package/templates/casting-history.json +4 -0
- package/templates/casting-policy.json +35 -0
- package/templates/casting-registry.json +3 -0
- package/templates/ceremonies.md +41 -0
- package/templates/charter.md +53 -0
- package/templates/constraint-tracking.md +38 -0
- package/templates/copilot-instructions.md +46 -0
- package/templates/history.md +10 -0
- package/templates/identity/now.md +9 -0
- package/templates/identity/wisdom.md +15 -0
- package/templates/mcp-config.md +98 -0
- package/templates/multi-agent-format.md +28 -0
- package/templates/orchestration-log.md +27 -0
- package/templates/plugin-marketplace.md +49 -0
- package/templates/raw-agent-output.md +37 -0
- package/templates/roster.md +60 -0
- package/templates/routing.md +54 -0
- package/templates/run-output.md +50 -0
- package/templates/scribe-charter.md +119 -0
- package/templates/skill.md +24 -0
- package/templates/skills/project-conventions/SKILL.md +56 -0
- package/templates/squad.agent.md +1146 -0
- package/templates/workflows/squad-ci.yml +24 -0
- package/templates/workflows/squad-docs.yml +50 -0
- package/templates/workflows/squad-heartbeat.yml +316 -0
- package/templates/workflows/squad-insider-release.yml +61 -0
- package/templates/workflows/squad-issue-assign.yml +161 -0
- package/templates/workflows/squad-label-enforce.yml +181 -0
- package/templates/workflows/squad-main-guard.yml +129 -0
- package/templates/workflows/squad-preview.yml +55 -0
- package/templates/workflows/squad-promote.yml +121 -0
- package/templates/workflows/squad-release.yml +77 -0
- package/templates/workflows/squad-triage.yml +260 -0
- package/templates/workflows/sync-squad-labels.yml +169 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
name: Squad Triage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [labeled]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
issues: write
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
triage:
|
|
13
|
+
if: github.event.label.name == 'squad'
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Triage issue via Lead agent
|
|
19
|
+
uses: actions/github-script@v7
|
|
20
|
+
with:
|
|
21
|
+
script: |
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const issue = context.payload.issue;
|
|
24
|
+
|
|
25
|
+
// Read team roster — check .squad/ first, fall back to .ai-team/
|
|
26
|
+
let teamFile = '.squad/team.md';
|
|
27
|
+
if (!fs.existsSync(teamFile)) {
|
|
28
|
+
teamFile = '.ai-team/team.md';
|
|
29
|
+
}
|
|
30
|
+
if (!fs.existsSync(teamFile)) {
|
|
31
|
+
core.warning('No .squad/team.md or .ai-team/team.md found — cannot triage');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const content = fs.readFileSync(teamFile, 'utf8');
|
|
36
|
+
const lines = content.split('\n');
|
|
37
|
+
|
|
38
|
+
// Check if @copilot is on the team
|
|
39
|
+
const hasCopilot = content.includes('🤖 Coding Agent');
|
|
40
|
+
const copilotAutoAssign = content.includes('<!-- copilot-auto-assign: true -->');
|
|
41
|
+
|
|
42
|
+
// Parse @copilot capability profile
|
|
43
|
+
let goodFitKeywords = [];
|
|
44
|
+
let needsReviewKeywords = [];
|
|
45
|
+
let notSuitableKeywords = [];
|
|
46
|
+
|
|
47
|
+
if (hasCopilot) {
|
|
48
|
+
// Extract capability tiers from team.md
|
|
49
|
+
const goodFitMatch = content.match(/🟢\s*Good fit[^:]*:\s*(.+)/i);
|
|
50
|
+
const needsReviewMatch = content.match(/🟡\s*Needs review[^:]*:\s*(.+)/i);
|
|
51
|
+
const notSuitableMatch = content.match(/🔴\s*Not suitable[^:]*:\s*(.+)/i);
|
|
52
|
+
|
|
53
|
+
if (goodFitMatch) {
|
|
54
|
+
goodFitKeywords = goodFitMatch[1].toLowerCase().split(',').map(s => s.trim());
|
|
55
|
+
} else {
|
|
56
|
+
goodFitKeywords = ['bug fix', 'test coverage', 'lint', 'format', 'dependency update', 'small feature', 'scaffolding', 'doc fix', 'documentation'];
|
|
57
|
+
}
|
|
58
|
+
if (needsReviewMatch) {
|
|
59
|
+
needsReviewKeywords = needsReviewMatch[1].toLowerCase().split(',').map(s => s.trim());
|
|
60
|
+
} else {
|
|
61
|
+
needsReviewKeywords = ['medium feature', 'refactoring', 'api endpoint', 'migration'];
|
|
62
|
+
}
|
|
63
|
+
if (notSuitableMatch) {
|
|
64
|
+
notSuitableKeywords = notSuitableMatch[1].toLowerCase().split(',').map(s => s.trim());
|
|
65
|
+
} else {
|
|
66
|
+
notSuitableKeywords = ['architecture', 'system design', 'security', 'auth', 'encryption', 'performance'];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const members = [];
|
|
71
|
+
let inMembersTable = false;
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
if (line.match(/^##\s+(Members|Team Roster)/i)) {
|
|
74
|
+
inMembersTable = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (inMembersTable && line.startsWith('## ')) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) {
|
|
81
|
+
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
82
|
+
if (cells.length >= 2 && cells[0] !== 'Scribe') {
|
|
83
|
+
members.push({
|
|
84
|
+
name: cells[0],
|
|
85
|
+
role: cells[1]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Read routing rules — check .squad/ first, fall back to .ai-team/
|
|
92
|
+
let routingFile = '.squad/routing.md';
|
|
93
|
+
if (!fs.existsSync(routingFile)) {
|
|
94
|
+
routingFile = '.ai-team/routing.md';
|
|
95
|
+
}
|
|
96
|
+
let routingContent = '';
|
|
97
|
+
if (fs.existsSync(routingFile)) {
|
|
98
|
+
routingContent = fs.readFileSync(routingFile, 'utf8');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Find the Lead
|
|
102
|
+
const lead = members.find(m =>
|
|
103
|
+
m.role.toLowerCase().includes('lead') ||
|
|
104
|
+
m.role.toLowerCase().includes('architect') ||
|
|
105
|
+
m.role.toLowerCase().includes('coordinator')
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!lead) {
|
|
109
|
+
core.warning('No Lead role found in team roster — cannot triage');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Build triage context
|
|
114
|
+
const memberList = members.map(m =>
|
|
115
|
+
`- **${m.name}** (${m.role}) → label: \`squad:${m.name.toLowerCase()}\``
|
|
116
|
+
).join('\n');
|
|
117
|
+
|
|
118
|
+
// Determine best assignee based on issue content and routing
|
|
119
|
+
const issueText = `${issue.title}\n${issue.body || ''}`.toLowerCase();
|
|
120
|
+
|
|
121
|
+
let assignedMember = null;
|
|
122
|
+
let triageReason = '';
|
|
123
|
+
let copilotTier = null;
|
|
124
|
+
|
|
125
|
+
// First, evaluate @copilot fit if enabled
|
|
126
|
+
if (hasCopilot) {
|
|
127
|
+
const isNotSuitable = notSuitableKeywords.some(kw => issueText.includes(kw));
|
|
128
|
+
const isGoodFit = !isNotSuitable && goodFitKeywords.some(kw => issueText.includes(kw));
|
|
129
|
+
const isNeedsReview = !isNotSuitable && !isGoodFit && needsReviewKeywords.some(kw => issueText.includes(kw));
|
|
130
|
+
|
|
131
|
+
if (isGoodFit) {
|
|
132
|
+
copilotTier = 'good-fit';
|
|
133
|
+
assignedMember = { name: '@copilot', role: 'Coding Agent' };
|
|
134
|
+
triageReason = '🟢 Good fit for @copilot — matches capability profile';
|
|
135
|
+
} else if (isNeedsReview) {
|
|
136
|
+
copilotTier = 'needs-review';
|
|
137
|
+
assignedMember = { name: '@copilot', role: 'Coding Agent' };
|
|
138
|
+
triageReason = '🟡 Routing to @copilot (needs review) — a squad member should review the PR';
|
|
139
|
+
} else if (isNotSuitable) {
|
|
140
|
+
copilotTier = 'not-suitable';
|
|
141
|
+
// Fall through to normal routing
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If not routed to @copilot, use keyword-based routing
|
|
146
|
+
if (!assignedMember) {
|
|
147
|
+
for (const member of members) {
|
|
148
|
+
const role = member.role.toLowerCase();
|
|
149
|
+
if ((role.includes('frontend') || role.includes('ui')) &&
|
|
150
|
+
(issueText.includes('ui') || issueText.includes('frontend') ||
|
|
151
|
+
issueText.includes('css') || issueText.includes('component') ||
|
|
152
|
+
issueText.includes('button') || issueText.includes('page') ||
|
|
153
|
+
issueText.includes('layout') || issueText.includes('design'))) {
|
|
154
|
+
assignedMember = member;
|
|
155
|
+
triageReason = 'Issue relates to frontend/UI work';
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if ((role.includes('backend') || role.includes('api') || role.includes('server')) &&
|
|
159
|
+
(issueText.includes('api') || issueText.includes('backend') ||
|
|
160
|
+
issueText.includes('database') || issueText.includes('endpoint') ||
|
|
161
|
+
issueText.includes('server') || issueText.includes('auth'))) {
|
|
162
|
+
assignedMember = member;
|
|
163
|
+
triageReason = 'Issue relates to backend/API work';
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
if ((role.includes('test') || role.includes('qa') || role.includes('quality')) &&
|
|
167
|
+
(issueText.includes('test') || issueText.includes('bug') ||
|
|
168
|
+
issueText.includes('fix') || issueText.includes('regression') ||
|
|
169
|
+
issueText.includes('coverage'))) {
|
|
170
|
+
assignedMember = member;
|
|
171
|
+
triageReason = 'Issue relates to testing/quality work';
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
if ((role.includes('devops') || role.includes('infra') || role.includes('ops')) &&
|
|
175
|
+
(issueText.includes('deploy') || issueText.includes('ci') ||
|
|
176
|
+
issueText.includes('pipeline') || issueText.includes('docker') ||
|
|
177
|
+
issueText.includes('infrastructure'))) {
|
|
178
|
+
assignedMember = member;
|
|
179
|
+
triageReason = 'Issue relates to DevOps/infrastructure work';
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Default to Lead if no routing match
|
|
186
|
+
if (!assignedMember) {
|
|
187
|
+
assignedMember = lead;
|
|
188
|
+
triageReason = 'No specific domain match — assigned to Lead for further analysis';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const isCopilot = assignedMember.name === '@copilot';
|
|
192
|
+
const assignLabel = isCopilot ? 'squad:copilot' : `squad:${assignedMember.name.toLowerCase()}`;
|
|
193
|
+
|
|
194
|
+
// Add the member-specific label
|
|
195
|
+
await github.rest.issues.addLabels({
|
|
196
|
+
owner: context.repo.owner,
|
|
197
|
+
repo: context.repo.repo,
|
|
198
|
+
issue_number: issue.number,
|
|
199
|
+
labels: [assignLabel]
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Apply default triage verdict
|
|
203
|
+
await github.rest.issues.addLabels({
|
|
204
|
+
owner: context.repo.owner,
|
|
205
|
+
repo: context.repo.repo,
|
|
206
|
+
issue_number: issue.number,
|
|
207
|
+
labels: ['go:needs-research']
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Auto-assign @copilot if enabled
|
|
211
|
+
if (isCopilot && copilotAutoAssign) {
|
|
212
|
+
try {
|
|
213
|
+
await github.rest.issues.addAssignees({
|
|
214
|
+
owner: context.repo.owner,
|
|
215
|
+
repo: context.repo.repo,
|
|
216
|
+
issue_number: issue.number,
|
|
217
|
+
assignees: ['copilot']
|
|
218
|
+
});
|
|
219
|
+
} catch (err) {
|
|
220
|
+
core.warning(`Could not auto-assign @copilot: ${err.message}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Build copilot evaluation note
|
|
225
|
+
let copilotNote = '';
|
|
226
|
+
if (hasCopilot && !isCopilot) {
|
|
227
|
+
if (copilotTier === 'not-suitable') {
|
|
228
|
+
copilotNote = `\n\n**@copilot evaluation:** 🔴 Not suitable — issue involves work outside the coding agent's capability profile.`;
|
|
229
|
+
} else {
|
|
230
|
+
copilotNote = `\n\n**@copilot evaluation:** No strong capability match — routed to squad member.`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Post triage comment
|
|
235
|
+
const comment = [
|
|
236
|
+
`### 🏗️ Squad Triage — ${lead.name} (${lead.role})`,
|
|
237
|
+
'',
|
|
238
|
+
`**Issue:** #${issue.number} — ${issue.title}`,
|
|
239
|
+
`**Assigned to:** ${assignedMember.name} (${assignedMember.role})`,
|
|
240
|
+
`**Reason:** ${triageReason}`,
|
|
241
|
+
copilotTier === 'needs-review' ? `\n⚠️ **PR review recommended** — a squad member should review @copilot's work on this one.` : '',
|
|
242
|
+
copilotNote,
|
|
243
|
+
'',
|
|
244
|
+
`---`,
|
|
245
|
+
'',
|
|
246
|
+
`**Team roster:**`,
|
|
247
|
+
memberList,
|
|
248
|
+
hasCopilot ? `- **@copilot** (Coding Agent) → label: \`squad:copilot\`` : '',
|
|
249
|
+
'',
|
|
250
|
+
`> To reassign, remove the current \`squad:*\` label and add the correct one.`,
|
|
251
|
+
].filter(Boolean).join('\n');
|
|
252
|
+
|
|
253
|
+
await github.rest.issues.createComment({
|
|
254
|
+
owner: context.repo.owner,
|
|
255
|
+
repo: context.repo.repo,
|
|
256
|
+
issue_number: issue.number,
|
|
257
|
+
body: comment
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
core.info(`Triaged issue #${issue.number} → ${assignedMember.name} (${assignLabel})`);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
name: Sync Squad Labels
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
paths:
|
|
6
|
+
- '.squad/team.md'
|
|
7
|
+
- '.ai-team/team.md'
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
issues: write
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
sync-labels:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Parse roster and sync labels
|
|
21
|
+
uses: actions/github-script@v7
|
|
22
|
+
with:
|
|
23
|
+
script: |
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
let teamFile = '.squad/team.md';
|
|
26
|
+
if (!fs.existsSync(teamFile)) {
|
|
27
|
+
teamFile = '.ai-team/team.md';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(teamFile)) {
|
|
31
|
+
core.info('No .squad/team.md or .ai-team/team.md found — skipping label sync');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const content = fs.readFileSync(teamFile, 'utf8');
|
|
36
|
+
const lines = content.split('\n');
|
|
37
|
+
|
|
38
|
+
// Parse the Members table for agent names
|
|
39
|
+
const members = [];
|
|
40
|
+
let inMembersTable = false;
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
if (line.match(/^##\s+(Members|Team Roster)/i)) {
|
|
43
|
+
inMembersTable = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (inMembersTable && line.startsWith('## ')) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) {
|
|
50
|
+
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
51
|
+
if (cells.length >= 2 && cells[0] !== 'Scribe') {
|
|
52
|
+
members.push({
|
|
53
|
+
name: cells[0],
|
|
54
|
+
role: cells[1]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
core.info(`Found ${members.length} squad members: ${members.map(m => m.name).join(', ')}`);
|
|
61
|
+
|
|
62
|
+
// Check if @copilot is on the team
|
|
63
|
+
const hasCopilot = content.includes('🤖 Coding Agent');
|
|
64
|
+
|
|
65
|
+
// Define label color palette for squad labels
|
|
66
|
+
const SQUAD_COLOR = '9B8FCC';
|
|
67
|
+
const MEMBER_COLOR = '9B8FCC';
|
|
68
|
+
const COPILOT_COLOR = '10b981';
|
|
69
|
+
|
|
70
|
+
// Define go: and release: labels (static)
|
|
71
|
+
const GO_LABELS = [
|
|
72
|
+
{ name: 'go:yes', color: '0E8A16', description: 'Ready to implement' },
|
|
73
|
+
{ name: 'go:no', color: 'B60205', description: 'Not pursuing' },
|
|
74
|
+
{ name: 'go:needs-research', color: 'FBCA04', description: 'Needs investigation' }
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const RELEASE_LABELS = [
|
|
78
|
+
{ name: 'release:v0.4.0', color: '6B8EB5', description: 'Targeted for v0.4.0' },
|
|
79
|
+
{ name: 'release:v0.5.0', color: '6B8EB5', description: 'Targeted for v0.5.0' },
|
|
80
|
+
{ name: 'release:v0.6.0', color: '8B7DB5', description: 'Targeted for v0.6.0' },
|
|
81
|
+
{ name: 'release:v1.0.0', color: '8B7DB5', description: 'Targeted for v1.0.0' },
|
|
82
|
+
{ name: 'release:backlog', color: 'D4E5F7', description: 'Not yet targeted' }
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const TYPE_LABELS = [
|
|
86
|
+
{ name: 'type:feature', color: 'DDD1F2', description: 'New capability' },
|
|
87
|
+
{ name: 'type:bug', color: 'FF0422', description: 'Something broken' },
|
|
88
|
+
{ name: 'type:spike', color: 'F2DDD4', description: 'Research/investigation — produces a plan, not code' },
|
|
89
|
+
{ name: 'type:docs', color: 'D4E5F7', description: 'Documentation work' },
|
|
90
|
+
{ name: 'type:chore', color: 'D4E5F7', description: 'Maintenance, refactoring, cleanup' },
|
|
91
|
+
{ name: 'type:epic', color: 'CC4455', description: 'Parent issue that decomposes into sub-issues' }
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// High-signal labels — these MUST visually dominate all others
|
|
95
|
+
const SIGNAL_LABELS = [
|
|
96
|
+
{ name: 'bug', color: 'FF0422', description: 'Something isn\'t working' },
|
|
97
|
+
{ name: 'feedback', color: '00E5FF', description: 'User feedback — high signal, needs attention' }
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const PRIORITY_LABELS = [
|
|
101
|
+
{ name: 'priority:p0', color: 'B60205', description: 'Blocking release' },
|
|
102
|
+
{ name: 'priority:p1', color: 'D93F0B', description: 'This sprint' },
|
|
103
|
+
{ name: 'priority:p2', color: 'FBCA04', description: 'Next sprint' }
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// Ensure the base "squad" triage label exists
|
|
107
|
+
const labels = [
|
|
108
|
+
{ name: 'squad', color: SQUAD_COLOR, description: 'Squad triage inbox — Lead will assign to a member' }
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
for (const member of members) {
|
|
112
|
+
labels.push({
|
|
113
|
+
name: `squad:${member.name.toLowerCase()}`,
|
|
114
|
+
color: MEMBER_COLOR,
|
|
115
|
+
description: `Assigned to ${member.name} (${member.role})`
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add @copilot label if coding agent is on the team
|
|
120
|
+
if (hasCopilot) {
|
|
121
|
+
labels.push({
|
|
122
|
+
name: 'squad:copilot',
|
|
123
|
+
color: COPILOT_COLOR,
|
|
124
|
+
description: 'Assigned to @copilot (Coding Agent) for autonomous work'
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add go:, release:, type:, priority:, and high-signal labels
|
|
129
|
+
labels.push(...GO_LABELS);
|
|
130
|
+
labels.push(...RELEASE_LABELS);
|
|
131
|
+
labels.push(...TYPE_LABELS);
|
|
132
|
+
labels.push(...PRIORITY_LABELS);
|
|
133
|
+
labels.push(...SIGNAL_LABELS);
|
|
134
|
+
|
|
135
|
+
// Sync labels (create or update)
|
|
136
|
+
for (const label of labels) {
|
|
137
|
+
try {
|
|
138
|
+
await github.rest.issues.getLabel({
|
|
139
|
+
owner: context.repo.owner,
|
|
140
|
+
repo: context.repo.repo,
|
|
141
|
+
name: label.name
|
|
142
|
+
});
|
|
143
|
+
// Label exists — update it
|
|
144
|
+
await github.rest.issues.updateLabel({
|
|
145
|
+
owner: context.repo.owner,
|
|
146
|
+
repo: context.repo.repo,
|
|
147
|
+
name: label.name,
|
|
148
|
+
color: label.color,
|
|
149
|
+
description: label.description
|
|
150
|
+
});
|
|
151
|
+
core.info(`Updated label: ${label.name}`);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (err.status === 404) {
|
|
154
|
+
// Label doesn't exist — create it
|
|
155
|
+
await github.rest.issues.createLabel({
|
|
156
|
+
owner: context.repo.owner,
|
|
157
|
+
repo: context.repo.repo,
|
|
158
|
+
name: label.name,
|
|
159
|
+
color: label.color,
|
|
160
|
+
description: label.description
|
|
161
|
+
});
|
|
162
|
+
core.info(`Created label: ${label.name}`);
|
|
163
|
+
} else {
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
core.info(`Label sync complete: ${labels.length} labels synced`);
|