@bradygaster/squad-sdk 0.9.0 → 0.9.1

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.
Files changed (76) hide show
  1. package/README.md +296 -296
  2. package/dist/agents/history-shadow.js +30 -30
  3. package/dist/build/github-dist.js +42 -42
  4. package/dist/config/init.js +173 -173
  5. package/dist/sharing/consult.js +78 -78
  6. package/package.json +1 -1
  7. package/templates/casting/Futurama.json +9 -9
  8. package/templates/casting-history.json +4 -4
  9. package/templates/casting-policy.json +37 -37
  10. package/templates/casting-reference.md +104 -104
  11. package/templates/casting-registry.json +3 -3
  12. package/templates/ceremonies.md +41 -41
  13. package/templates/charter.md +53 -53
  14. package/templates/constraint-tracking.md +38 -38
  15. package/templates/cooperative-rate-limiting.md +229 -229
  16. package/templates/copilot-instructions.md +46 -46
  17. package/templates/history.md +10 -10
  18. package/templates/identity/now.md +9 -9
  19. package/templates/identity/wisdom.md +15 -15
  20. package/templates/issue-lifecycle.md +412 -412
  21. package/templates/keda-scaler.md +164 -164
  22. package/templates/machine-capabilities.md +74 -74
  23. package/templates/mcp-config.md +90 -90
  24. package/templates/multi-agent-format.md +28 -28
  25. package/templates/plugin-marketplace.md +49 -49
  26. package/templates/ralph-circuit-breaker.md +313 -313
  27. package/templates/raw-agent-output.md +37 -37
  28. package/templates/roster.md +60 -60
  29. package/templates/routing.md +39 -39
  30. package/templates/run-output.md +50 -50
  31. package/templates/schedule.json +19 -19
  32. package/templates/scribe-charter.md +119 -119
  33. package/templates/skill.md +24 -24
  34. package/templates/skills/agent-collaboration/SKILL.md +42 -42
  35. package/templates/skills/agent-conduct/SKILL.md +24 -24
  36. package/templates/skills/architectural-proposals/SKILL.md +151 -151
  37. package/templates/skills/ci-validation-gates/SKILL.md +84 -84
  38. package/templates/skills/cli-wiring/SKILL.md +47 -47
  39. package/templates/skills/client-compatibility/SKILL.md +89 -89
  40. package/templates/skills/cross-squad/SKILL.md +114 -114
  41. package/templates/skills/distributed-mesh/SKILL.md +287 -287
  42. package/templates/skills/distributed-mesh/mesh.json.example +30 -30
  43. package/templates/skills/distributed-mesh/sync-mesh.ps1 +111 -111
  44. package/templates/skills/distributed-mesh/sync-mesh.sh +104 -104
  45. package/templates/skills/docs-standards/SKILL.md +71 -71
  46. package/templates/skills/economy-mode/SKILL.md +114 -114
  47. package/templates/skills/external-comms/SKILL.md +329 -329
  48. package/templates/skills/gh-auth-isolation/SKILL.md +183 -183
  49. package/templates/skills/git-workflow/SKILL.md +204 -204
  50. package/templates/skills/github-multi-account/SKILL.md +95 -95
  51. package/templates/skills/history-hygiene/SKILL.md +36 -36
  52. package/templates/skills/humanizer/SKILL.md +105 -105
  53. package/templates/skills/init-mode/SKILL.md +102 -102
  54. package/templates/skills/model-selection/SKILL.md +117 -117
  55. package/templates/skills/nap/SKILL.md +24 -24
  56. package/templates/skills/personal-squad/SKILL.md +57 -57
  57. package/templates/skills/project-conventions/SKILL.md +56 -56
  58. package/templates/skills/release-process/SKILL.md +423 -423
  59. package/templates/skills/reskill/SKILL.md +92 -92
  60. package/templates/skills/reviewer-protocol/SKILL.md +79 -79
  61. package/templates/skills/secret-handling/SKILL.md +200 -200
  62. package/templates/skills/session-recovery/SKILL.md +155 -155
  63. package/templates/skills/squad-conventions/SKILL.md +69 -69
  64. package/templates/skills/test-discipline/SKILL.md +37 -37
  65. package/templates/skills/windows-compatibility/SKILL.md +74 -74
  66. package/templates/workflows/squad-ci.yml +24 -24
  67. package/templates/workflows/squad-docs.yml +54 -54
  68. package/templates/workflows/squad-heartbeat.yml +171 -171
  69. package/templates/workflows/squad-insider-release.yml +61 -61
  70. package/templates/workflows/squad-issue-assign.yml +161 -161
  71. package/templates/workflows/squad-label-enforce.yml +181 -181
  72. package/templates/workflows/squad-preview.yml +55 -55
  73. package/templates/workflows/squad-promote.yml +120 -120
  74. package/templates/workflows/squad-release.yml +77 -77
  75. package/templates/workflows/squad-triage.yml +260 -260
  76. package/templates/workflows/sync-squad-labels.yml +169 -169
@@ -1,74 +1,74 @@
1
- ---
2
- name: "windows-compatibility"
3
- description: "Cross-platform path handling and command patterns"
4
- domain: "platform"
5
- confidence: "high"
6
- source: "earned (multiple Windows-specific bugs: colons in filenames, git -C failures, path separators)"
7
- ---
8
-
9
- ## Context
10
-
11
- Squad runs on Windows, macOS, and Linux. Several bugs have been traced to platform-specific assumptions: ISO timestamps with colons (illegal on Windows), `git -C` with Windows paths (unreliable), forward-slash paths in Node.js on Windows.
12
-
13
- ## Patterns
14
-
15
- ### Filenames & Timestamps
16
- - **Never use colons in filenames:** ISO 8601 format `2026-03-15T05:30:00Z` is illegal on Windows
17
- - **Use `safeTimestamp()` utility:** Replaces colons with hyphens → `2026-03-15T05-30-00Z`
18
- - **Centralize formatting:** Don't inline `.toISOString().replace(/:/g, '-')` — use the utility
19
-
20
- ### Git Commands
21
- - **Never use `git -C {path}`:** Unreliable with Windows paths (backslashes, spaces, drive letters)
22
- - **Always `cd` first:** Change directory, then run git commands
23
- - **Check for changes before commit:** `git diff --cached --quiet` (exit 0 = no changes)
24
-
25
- ### Commit Messages
26
- - **Never embed newlines in `-m` flag:** Backtick-n (`\n`) fails silently in PowerShell
27
- - **Use temp file + `-F` flag:** Write message to file, commit with `git commit -F $msgFile`
28
-
29
- ### Paths
30
- - **Never assume CWD is repo root:** Always use `TEAM ROOT` from spawn prompt or run `git rev-parse --show-toplevel`
31
- - **Use path.join() or path.resolve():** Don't manually concatenate with `/` or `\`
32
-
33
- ## Examples
34
-
35
- ✓ **Correct:**
36
- ```javascript
37
- // Timestamp utility
38
- const safeTimestamp = () => new Date().toISOString().replace(/:/g, '-').split('.')[0] + 'Z';
39
-
40
- // Git workflow (PowerShell)
41
- cd $teamRoot
42
- git add .squad/
43
- if ($LASTEXITCODE -eq 0) {
44
- $msg = @"
45
- docs(ai-team): session log
46
-
47
- Changes:
48
- - Added decisions
49
- "@
50
- $msgFile = [System.IO.Path]::GetTempFileName()
51
- Set-Content -Path $msgFile -Value $msg -Encoding utf8
52
- git commit -F $msgFile
53
- Remove-Item $msgFile
54
- }
55
- ```
56
-
57
- ✗ **Incorrect:**
58
- ```javascript
59
- // Colon in filename
60
- const logPath = `.squad/log/${new Date().toISOString()}.md`; // ILLEGAL on Windows
61
-
62
- // git -C with Windows path
63
- exec('git -C C:\\src\\squad add .squad/'); // UNRELIABLE
64
-
65
- // Inline newlines in commit message
66
- exec('git commit -m "First line\nSecond line"'); // FAILS silently in PowerShell
67
- ```
68
-
69
- ## Anti-Patterns
70
-
71
- - Testing only on one platform (bugs ship to other platforms)
72
- - Assuming Unix-style paths work everywhere
73
- - Using `git -C` because it "looks cleaner" (it doesn't work)
74
- - Skipping `git diff --cached --quiet` check (creates empty commits)
1
+ ---
2
+ name: "windows-compatibility"
3
+ description: "Cross-platform path handling and command patterns"
4
+ domain: "platform"
5
+ confidence: "high"
6
+ source: "earned (multiple Windows-specific bugs: colons in filenames, git -C failures, path separators)"
7
+ ---
8
+
9
+ ## Context
10
+
11
+ Squad runs on Windows, macOS, and Linux. Several bugs have been traced to platform-specific assumptions: ISO timestamps with colons (illegal on Windows), `git -C` with Windows paths (unreliable), forward-slash paths in Node.js on Windows.
12
+
13
+ ## Patterns
14
+
15
+ ### Filenames & Timestamps
16
+ - **Never use colons in filenames:** ISO 8601 format `2026-03-15T05:30:00Z` is illegal on Windows
17
+ - **Use `safeTimestamp()` utility:** Replaces colons with hyphens → `2026-03-15T05-30-00Z`
18
+ - **Centralize formatting:** Don't inline `.toISOString().replace(/:/g, '-')` — use the utility
19
+
20
+ ### Git Commands
21
+ - **Never use `git -C {path}`:** Unreliable with Windows paths (backslashes, spaces, drive letters)
22
+ - **Always `cd` first:** Change directory, then run git commands
23
+ - **Check for changes before commit:** `git diff --cached --quiet` (exit 0 = no changes)
24
+
25
+ ### Commit Messages
26
+ - **Never embed newlines in `-m` flag:** Backtick-n (`\n`) fails silently in PowerShell
27
+ - **Use temp file + `-F` flag:** Write message to file, commit with `git commit -F $msgFile`
28
+
29
+ ### Paths
30
+ - **Never assume CWD is repo root:** Always use `TEAM ROOT` from spawn prompt or run `git rev-parse --show-toplevel`
31
+ - **Use path.join() or path.resolve():** Don't manually concatenate with `/` or `\`
32
+
33
+ ## Examples
34
+
35
+ ✓ **Correct:**
36
+ ```javascript
37
+ // Timestamp utility
38
+ const safeTimestamp = () => new Date().toISOString().replace(/:/g, '-').split('.')[0] + 'Z';
39
+
40
+ // Git workflow (PowerShell)
41
+ cd $teamRoot
42
+ git add .squad/
43
+ if ($LASTEXITCODE -eq 0) {
44
+ $msg = @"
45
+ docs(ai-team): session log
46
+
47
+ Changes:
48
+ - Added decisions
49
+ "@
50
+ $msgFile = [System.IO.Path]::GetTempFileName()
51
+ Set-Content -Path $msgFile -Value $msg -Encoding utf8
52
+ git commit -F $msgFile
53
+ Remove-Item $msgFile
54
+ }
55
+ ```
56
+
57
+ ✗ **Incorrect:**
58
+ ```javascript
59
+ // Colon in filename
60
+ const logPath = `.squad/log/${new Date().toISOString()}.md`; // ILLEGAL on Windows
61
+
62
+ // git -C with Windows path
63
+ exec('git -C C:\\src\\squad add .squad/'); // UNRELIABLE
64
+
65
+ // Inline newlines in commit message
66
+ exec('git commit -m "First line\nSecond line"'); // FAILS silently in PowerShell
67
+ ```
68
+
69
+ ## Anti-Patterns
70
+
71
+ - Testing only on one platform (bugs ship to other platforms)
72
+ - Assuming Unix-style paths work everywhere
73
+ - Using `git -C` because it "looks cleaner" (it doesn't work)
74
+ - Skipping `git diff --cached --quiet` check (creates empty commits)
@@ -1,24 +1,24 @@
1
- name: Squad CI
2
-
3
- on:
4
- pull_request:
5
- branches: [dev, preview, main, insider]
6
- types: [opened, synchronize, reopened]
7
- push:
8
- branches: [dev, insider]
9
-
10
- permissions:
11
- contents: read
12
-
13
- jobs:
14
- test:
15
- runs-on: ubuntu-latest
16
- steps:
17
- - uses: actions/checkout@v4
18
-
19
- - uses: actions/setup-node@v4
20
- with:
21
- node-version: 22
22
-
23
- - name: Run tests
24
- run: node --test test/*.test.js
1
+ name: Squad CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [dev, preview, main, insider]
6
+ types: [opened, synchronize, reopened]
7
+ push:
8
+ branches: [dev, insider]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 22
22
+
23
+ - name: Run tests
24
+ run: node --test test/*.test.js
@@ -1,54 +1,54 @@
1
- name: Squad Docs — Build & Deploy
2
-
3
- on:
4
- workflow_dispatch:
5
- push:
6
- branches: [preview]
7
- paths:
8
- - 'docs/**'
9
- - '.github/workflows/squad-docs.yml'
10
-
11
- permissions:
12
- contents: read
13
- pages: write
14
- id-token: write
15
-
16
- concurrency:
17
- group: pages
18
- cancel-in-progress: true
19
-
20
- jobs:
21
- build:
22
- runs-on: ubuntu-latest
23
- steps:
24
- - uses: actions/checkout@v4
25
-
26
- - uses: actions/setup-node@v4
27
- with:
28
- node-version: '22'
29
- cache: npm
30
- cache-dependency-path: docs/package-lock.json
31
-
32
- - name: Install docs dependencies
33
- working-directory: docs
34
- run: npm ci
35
-
36
- - name: Build docs site
37
- working-directory: docs
38
- run: npm run build
39
-
40
- - name: Upload Pages artifact
41
- uses: actions/upload-pages-artifact@v3
42
- with:
43
- path: docs/dist
44
-
45
- deploy:
46
- needs: build
47
- runs-on: ubuntu-latest
48
- environment:
49
- name: github-pages
50
- url: ${{ steps.deployment.outputs.page_url }}
51
- steps:
52
- - name: Deploy to GitHub Pages
53
- id: deployment
54
- uses: actions/deploy-pages@v4
1
+ name: Squad Docs — Build & Deploy
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches: [preview]
7
+ paths:
8
+ - 'docs/**'
9
+ - '.github/workflows/squad-docs.yml'
10
+
11
+ permissions:
12
+ contents: read
13
+ pages: write
14
+ id-token: write
15
+
16
+ concurrency:
17
+ group: pages
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - uses: actions/setup-node@v4
27
+ with:
28
+ node-version: '22'
29
+ cache: npm
30
+ cache-dependency-path: docs/package-lock.json
31
+
32
+ - name: Install docs dependencies
33
+ working-directory: docs
34
+ run: npm ci
35
+
36
+ - name: Build docs site
37
+ working-directory: docs
38
+ run: npm run build
39
+
40
+ - name: Upload Pages artifact
41
+ uses: actions/upload-pages-artifact@v3
42
+ with:
43
+ path: docs/dist
44
+
45
+ deploy:
46
+ needs: build
47
+ runs-on: ubuntu-latest
48
+ environment:
49
+ name: github-pages
50
+ url: ${{ steps.deployment.outputs.page_url }}
51
+ steps:
52
+ - name: Deploy to GitHub Pages
53
+ id: deployment
54
+ uses: actions/deploy-pages@v4
@@ -1,171 +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
- }
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
+ }