Skip to content

Commit fd8abec

Browse files
authored
Merge pull request #115 from fboucher/v-next
V next
2 parents 069c21d + 3d4718c commit fd8abec

File tree

113 files changed

+11491
-159
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

113 files changed

+11491
-159
lines changed

.gitattributes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,8 @@
33
.ai-team/agents/*/history.md merge=union
44
.ai-team/log/** merge=union
55
.ai-team/orchestration-log/** merge=union
6+
# Squad: union merge for append-only team state files
7+
.squad/decisions.md merge=union
8+
.squad/agents/*/history.md merge=union
9+
.squad/log/** merge=union
10+
.squad/orchestration-log/** merge=union
Lines changed: 94 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,94 @@
1-
name: Unit Tests
2-
3-
on:
4-
push:
5-
branches:
6-
- main
7-
pull_request:
8-
branches:
9-
- main
10-
- v-next
11-
12-
permissions:
13-
contents: read
14-
checks: write
15-
pull-requests: write
16-
17-
env:
18-
DEFAULT_DOTNET_VERSION: "10.0.x"
19-
20-
jobs:
21-
test:
22-
runs-on: ubuntu-latest
23-
name: Run Unit Tests
24-
25-
steps:
26-
- name: Checkout
27-
uses: actions/checkout@v4
28-
29-
- name: Setup .NET
30-
uses: actions/setup-dotnet@v4
31-
with:
32-
dotnet-version: |
33-
${{ env.DEFAULT_DOTNET_VERSION }}
34-
10.0.x
35-
36-
- name: Restore dependencies
37-
run: dotnet restore
38-
39-
- name: Build solution
40-
run: dotnet build --no-restore --configuration Release
41-
42-
- name: Run unit tests
43-
run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./TestResults --logger trx --logger "console;verbosity=detailed"
44-
45-
- name: Publish Test Results
46-
uses: dorny/test-reporter@v1
47-
if: always()
48-
with:
49-
name: .NET Tests
50-
path: ./TestResults/**/*.trx
51-
reporter: dotnet-trx
52-
fail-on-error: true
53-
54-
- name: Code Coverage Report
55-
uses: irongut/CodeCoverageSummary@v1.3.0
56-
if: always()
57-
with:
58-
filename: ./TestResults/**/coverage.cobertura.xml
59-
badge: true
60-
fail_below_min: false
61-
format: markdown
62-
hide_branch_rate: false
63-
hide_complexity: true
64-
indicators: true
65-
output: both
66-
thresholds: '60 80'
67-
68-
- name: Add Coverage PR Comment
69-
uses: marocchino/sticky-pull-request-comment@v2
70-
if: github.event_name == 'pull_request'
71-
with:
72-
recreate: true
73-
path: code-coverage-results.md
74-
75-
- name: Upload test results
76-
uses: actions/upload-artifact@v4
77-
if: always()
78-
with:
79-
name: test-results-${{ github.run_number }}
80-
path: ./TestResults
81-
82-
- name: Upload coverage reports
83-
uses: actions/upload-artifact@v4
84-
if: always()
85-
with:
86-
name: coverage-reports-${{ github.run_number }}
87-
path: ./TestResults/**/coverage.cobertura.xml
1+
name: Unit Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
- v-next
11+
12+
permissions:
13+
contents: read
14+
checks: write
15+
pull-requests: write
16+
17+
env:
18+
DEFAULT_DOTNET_VERSION: "10.0.x"
19+
20+
jobs:
21+
test:
22+
runs-on: ubuntu-latest
23+
name: Run Unit Tests
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Setup .NET
30+
uses: actions/setup-dotnet@v4
31+
with:
32+
dotnet-version: |
33+
${{ env.DEFAULT_DOTNET_VERSION }}
34+
10.0.x
35+
36+
- name: Restore dependencies
37+
run: dotnet restore
38+
39+
- name: Build solution
40+
run: dotnet build --no-restore --configuration Release
41+
42+
- name: Run unit tests
43+
run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./TestResults --logger trx --logger "console;verbosity=detailed"
44+
45+
- name: Publish Test Results
46+
uses: dorny/test-reporter@v1
47+
if: always()
48+
with:
49+
name: .NET Tests
50+
path: ./TestResults/**/*.trx
51+
reporter: dotnet-trx
52+
fail-on-error: true
53+
54+
- name: Install ReportGenerator
55+
run: dotnet tool install -g dotnet-reportgenerator-globaltool
56+
57+
- name: ReportGenerator - Merge & Deduplicate Coverage
58+
run: |
59+
reportgenerator -reports:"./TestResults/**/*.cobertura.xml" -targetdir:./CodeCoverage -reporttypes:Cobertura -title:"Coverage Summary"
60+
61+
- name: Code Coverage Report
62+
uses: irongut/CodeCoverageSummary@v1.3.0
63+
if: always()
64+
with:
65+
filename: ./CodeCoverage/Cobertura.xml
66+
badge: true
67+
fail_below_min: false
68+
format: markdown
69+
hide_branch_rate: false
70+
hide_complexity: true
71+
indicators: true
72+
output: both
73+
thresholds: '60 80'
74+
75+
- name: Add Coverage PR Comment
76+
uses: marocchino/sticky-pull-request-comment@v2
77+
if: github.event_name == 'pull_request'
78+
with:
79+
recreate: true
80+
path: code-coverage-results.md
81+
82+
- name: Upload test results
83+
uses: actions/upload-artifact@v4
84+
if: always()
85+
with:
86+
name: test-results-${{ github.run_number }}
87+
path: ./TestResults
88+
89+
- name: Upload coverage reports
90+
uses: actions/upload-artifact@v4
91+
if: always()
92+
with:
93+
name: coverage-reports-${{ github.run_number }}
94+
path: ./CodeCoverage
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
name: Squad Heartbeat (Ralph)
2+
# ⚠️ SYNC: This workflow is maintained in 4 locations. Changes must be applied to all:
3+
# - templates/workflows/squad-heartbeat.yml (source template)
4+
# - packages/squad-cli/templates/workflows/squad-heartbeat.yml (CLI package)
5+
# - .squad/templates/workflows/squad-heartbeat.yml (installed template)
6+
# - .github/workflows/squad-heartbeat.yml (active workflow)
7+
# Run 'squad upgrade' to sync installed copies from source templates.
8+
9+
on:
10+
schedule:
11+
# Every 30 minutes — adjust via cron expression as needed
12+
- cron: '*/30 * * * *'
13+
14+
# React to completed work or new squad work
15+
issues:
16+
types: [closed, labeled]
17+
pull_request:
18+
types: [closed]
19+
20+
# Manual trigger
21+
workflow_dispatch:
22+
23+
permissions:
24+
issues: write
25+
contents: read
26+
pull-requests: read
27+
28+
jobs:
29+
heartbeat:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- name: Check triage script
35+
id: check-script
36+
run: |
37+
if [ -f ".squad/templates/ralph-triage.js" ]; then
38+
echo "has_script=true" >> $GITHUB_OUTPUT
39+
else
40+
echo "has_script=false" >> $GITHUB_OUTPUT
41+
echo "⚠️ ralph-triage.js not found — run 'squad upgrade' to install"
42+
fi
43+
44+
- name: Ralph — Smart triage
45+
if: steps.check-script.outputs.has_script == 'true'
46+
env:
47+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48+
run: |
49+
node .squad/templates/ralph-triage.js \
50+
--squad-dir .squad \
51+
--output triage-results.json
52+
53+
- name: Ralph — Apply triage decisions
54+
if: steps.check-script.outputs.has_script == 'true' && hashFiles('triage-results.json') != ''
55+
uses: actions/github-script@v7
56+
with:
57+
script: |
58+
const fs = require('fs');
59+
const path = 'triage-results.json';
60+
if (!fs.existsSync(path)) {
61+
core.info('No triage results — board is clear');
62+
return;
63+
}
64+
65+
const results = JSON.parse(fs.readFileSync(path, 'utf8'));
66+
if (results.length === 0) {
67+
core.info('📋 Board is clear — Ralph found no untriaged issues');
68+
return;
69+
}
70+
71+
for (const decision of results) {
72+
try {
73+
await github.rest.issues.addLabels({
74+
owner: context.repo.owner,
75+
repo: context.repo.repo,
76+
issue_number: decision.issueNumber,
77+
labels: [decision.label]
78+
});
79+
80+
await github.rest.issues.createComment({
81+
owner: context.repo.owner,
82+
repo: context.repo.repo,
83+
issue_number: decision.issueNumber,
84+
body: [
85+
'### 🔄 Ralph — Auto-Triage',
86+
'',
87+
`**Assigned to:** ${decision.assignTo}`,
88+
`**Reason:** ${decision.reason}`,
89+
`**Source:** ${decision.source}`,
90+
'',
91+
'> Ralph auto-triaged this issue using routing rules.',
92+
'> To reassign, swap the `squad:*` label.'
93+
].join('\n')
94+
});
95+
96+
core.info(`Triaged #${decision.issueNumber} → ${decision.assignTo} (${decision.source})`);
97+
} catch (e) {
98+
core.warning(`Failed to triage #${decision.issueNumber}: ${e.message}`);
99+
}
100+
}
101+
102+
core.info(`🔄 Ralph triaged ${results.length} issue(s)`);
103+
104+
# Copilot auto-assign step (uses PAT if available)
105+
- name: Ralph — Assign @copilot issues
106+
if: success()
107+
uses: actions/github-script@v7
108+
with:
109+
github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }}
110+
script: |
111+
const fs = require('fs');
112+
113+
let teamFile = '.squad/team.md';
114+
if (!fs.existsSync(teamFile)) {
115+
teamFile = '.ai-team/team.md';
116+
}
117+
if (!fs.existsSync(teamFile)) return;
118+
119+
const content = fs.readFileSync(teamFile, 'utf8');
120+
121+
// Check if @copilot is on the team with auto-assign
122+
const hasCopilot = content.includes('🤖 Coding Agent') || content.includes('@copilot');
123+
const autoAssign = content.includes('<!-- copilot-auto-assign: true -->');
124+
if (!hasCopilot || !autoAssign) return;
125+
126+
// Find issues labeled squad:copilot with no assignee
127+
try {
128+
const { data: copilotIssues } = await github.rest.issues.listForRepo({
129+
owner: context.repo.owner,
130+
repo: context.repo.repo,
131+
labels: 'squad:copilot',
132+
state: 'open',
133+
per_page: 5
134+
});
135+
136+
const unassigned = copilotIssues.filter(i =>
137+
!i.assignees || i.assignees.length === 0
138+
);
139+
140+
if (unassigned.length === 0) {
141+
core.info('No unassigned squad:copilot issues');
142+
return;
143+
}
144+
145+
// Get repo default branch
146+
const { data: repoData } = await github.rest.repos.get({
147+
owner: context.repo.owner,
148+
repo: context.repo.repo
149+
});
150+
151+
for (const issue of unassigned) {
152+
try {
153+
await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', {
154+
owner: context.repo.owner,
155+
repo: context.repo.repo,
156+
issue_number: issue.number,
157+
assignees: ['copilot-swe-agent[bot]'],
158+
agent_assignment: {
159+
target_repo: `${context.repo.owner}/${context.repo.repo}`,
160+
base_branch: repoData.default_branch,
161+
custom_instructions: `Read .squad/team.md (or .ai-team/team.md) for team context and .squad/routing.md (or .ai-team/routing.md) for routing rules.`
162+
}
163+
});
164+
core.info(`Assigned copilot-swe-agent[bot] to #${issue.number}`);
165+
} catch (e) {
166+
core.warning(`Failed to assign @copilot to #${issue.number}: ${e.message}`);
167+
}
168+
}
169+
} catch (e) {
170+
core.info(`No squad:copilot label found or error: ${e.message}`);
171+
}

0 commit comments

Comments
 (0)