@bradygaster/squad-cli 0.8.5 → 0.8.17-preview

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 (153) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/commands/copilot-bridge.d.ts +42 -0
  3. package/dist/cli/commands/copilot-bridge.d.ts.map +1 -0
  4. package/dist/cli/commands/copilot-bridge.js +191 -0
  5. package/dist/cli/commands/copilot-bridge.js.map +1 -0
  6. package/dist/cli/commands/import.js.map +1 -1
  7. package/dist/cli/commands/rc-tunnel.d.ts +30 -0
  8. package/dist/cli/commands/rc-tunnel.d.ts.map +1 -0
  9. package/dist/cli/commands/rc-tunnel.js +107 -0
  10. package/dist/cli/commands/rc-tunnel.js.map +1 -0
  11. package/dist/cli/commands/rc.d.ts +13 -0
  12. package/dist/cli/commands/rc.d.ts.map +1 -0
  13. package/dist/cli/commands/rc.js +270 -0
  14. package/dist/cli/commands/rc.js.map +1 -0
  15. package/dist/cli/commands/start.d.ts +18 -0
  16. package/dist/cli/commands/start.d.ts.map +1 -0
  17. package/dist/cli/commands/start.js +219 -0
  18. package/dist/cli/commands/start.js.map +1 -0
  19. package/dist/cli/commands/upstream.js.map +1 -1
  20. package/dist/cli/commands/watch.d.ts +10 -0
  21. package/dist/cli/commands/watch.d.ts.map +1 -1
  22. package/dist/cli/commands/watch.js +181 -65
  23. package/dist/cli/commands/watch.js.map +1 -1
  24. package/dist/cli/core/cast.d.ts +40 -0
  25. package/dist/cli/core/cast.d.ts.map +1 -0
  26. package/dist/cli/core/cast.js +442 -0
  27. package/dist/cli/core/cast.js.map +1 -0
  28. package/dist/cli/core/gh-cli.d.ts +25 -0
  29. package/dist/cli/core/gh-cli.d.ts.map +1 -1
  30. package/dist/cli/core/gh-cli.js +15 -1
  31. package/dist/cli/core/gh-cli.js.map +1 -1
  32. package/dist/cli/core/init.d.ts +9 -1
  33. package/dist/cli/core/init.d.ts.map +1 -1
  34. package/dist/cli/core/init.js +108 -13
  35. package/dist/cli/core/init.js.map +1 -1
  36. package/dist/cli/core/migrations.js.map +1 -1
  37. package/dist/cli/core/nap.d.ts +37 -0
  38. package/dist/cli/core/nap.d.ts.map +1 -0
  39. package/dist/cli/core/nap.js +528 -0
  40. package/dist/cli/core/nap.js.map +1 -0
  41. package/dist/cli/core/output.d.ts +5 -0
  42. package/dist/cli/core/output.d.ts.map +1 -1
  43. package/dist/cli/core/output.js +7 -0
  44. package/dist/cli/core/output.js.map +1 -1
  45. package/dist/cli/core/upgrade.d.ts +0 -1
  46. package/dist/cli/core/upgrade.d.ts.map +1 -1
  47. package/dist/cli/core/upgrade.js.map +1 -1
  48. package/dist/cli/core/version.js.map +1 -1
  49. package/dist/cli/shell/agent-status.d.ts +11 -0
  50. package/dist/cli/shell/agent-status.d.ts.map +1 -0
  51. package/dist/cli/shell/agent-status.js +26 -0
  52. package/dist/cli/shell/agent-status.js.map +1 -0
  53. package/dist/cli/shell/commands.d.ts +10 -0
  54. package/dist/cli/shell/commands.d.ts.map +1 -1
  55. package/dist/cli/shell/commands.js +143 -29
  56. package/dist/cli/shell/commands.js.map +1 -1
  57. package/dist/cli/shell/components/AgentPanel.d.ts +1 -4
  58. package/dist/cli/shell/components/AgentPanel.d.ts.map +1 -1
  59. package/dist/cli/shell/components/AgentPanel.js +88 -6
  60. package/dist/cli/shell/components/AgentPanel.js.map +1 -1
  61. package/dist/cli/shell/components/App.d.ts +11 -6
  62. package/dist/cli/shell/components/App.d.ts.map +1 -1
  63. package/dist/cli/shell/components/App.js +212 -35
  64. package/dist/cli/shell/components/App.js.map +1 -1
  65. package/dist/cli/shell/components/ErrorBoundary.d.ts +22 -0
  66. package/dist/cli/shell/components/ErrorBoundary.d.ts.map +1 -0
  67. package/dist/cli/shell/components/ErrorBoundary.js +31 -0
  68. package/dist/cli/shell/components/ErrorBoundary.js.map +1 -0
  69. package/dist/cli/shell/components/InputPrompt.d.ts +3 -0
  70. package/dist/cli/shell/components/InputPrompt.d.ts.map +1 -1
  71. package/dist/cli/shell/components/InputPrompt.js +155 -13
  72. package/dist/cli/shell/components/InputPrompt.js.map +1 -1
  73. package/dist/cli/shell/components/MessageStream.d.ts +17 -4
  74. package/dist/cli/shell/components/MessageStream.d.ts.map +1 -1
  75. package/dist/cli/shell/components/MessageStream.js +215 -23
  76. package/dist/cli/shell/components/MessageStream.js.map +1 -1
  77. package/dist/cli/shell/components/Separator.d.ts +17 -0
  78. package/dist/cli/shell/components/Separator.d.ts.map +1 -0
  79. package/dist/cli/shell/components/Separator.js +10 -0
  80. package/dist/cli/shell/components/Separator.js.map +1 -0
  81. package/dist/cli/shell/components/ThinkingIndicator.d.ts +21 -0
  82. package/dist/cli/shell/components/ThinkingIndicator.d.ts.map +1 -0
  83. package/dist/cli/shell/components/ThinkingIndicator.js +102 -0
  84. package/dist/cli/shell/components/ThinkingIndicator.js.map +1 -0
  85. package/dist/cli/shell/components/index.d.ts +3 -0
  86. package/dist/cli/shell/components/index.d.ts.map +1 -1
  87. package/dist/cli/shell/components/index.js +2 -0
  88. package/dist/cli/shell/components/index.js.map +1 -1
  89. package/dist/cli/shell/coordinator.d.ts +10 -0
  90. package/dist/cli/shell/coordinator.d.ts.map +1 -1
  91. package/dist/cli/shell/coordinator.js +99 -4
  92. package/dist/cli/shell/coordinator.js.map +1 -1
  93. package/dist/cli/shell/error-messages.d.ts +21 -0
  94. package/dist/cli/shell/error-messages.d.ts.map +1 -0
  95. package/dist/cli/shell/error-messages.js +61 -0
  96. package/dist/cli/shell/error-messages.js.map +1 -0
  97. package/dist/cli/shell/index.d.ts +24 -3
  98. package/dist/cli/shell/index.d.ts.map +1 -1
  99. package/dist/cli/shell/index.js +943 -34
  100. package/dist/cli/shell/index.js.map +1 -1
  101. package/dist/cli/shell/lifecycle.d.ts +2 -0
  102. package/dist/cli/shell/lifecycle.d.ts.map +1 -1
  103. package/dist/cli/shell/lifecycle.js +59 -6
  104. package/dist/cli/shell/lifecycle.js.map +1 -1
  105. package/dist/cli/shell/memory.d.ts +6 -1
  106. package/dist/cli/shell/memory.d.ts.map +1 -1
  107. package/dist/cli/shell/memory.js +12 -1
  108. package/dist/cli/shell/memory.js.map +1 -1
  109. package/dist/cli/shell/router.d.ts +16 -0
  110. package/dist/cli/shell/router.d.ts.map +1 -1
  111. package/dist/cli/shell/router.js +27 -0
  112. package/dist/cli/shell/router.js.map +1 -1
  113. package/dist/cli/shell/session-store.d.ts +47 -0
  114. package/dist/cli/shell/session-store.d.ts.map +1 -0
  115. package/dist/cli/shell/session-store.js +125 -0
  116. package/dist/cli/shell/session-store.js.map +1 -0
  117. package/dist/cli/shell/sessions.d.ts +2 -0
  118. package/dist/cli/shell/sessions.d.ts.map +1 -1
  119. package/dist/cli/shell/sessions.js +19 -5
  120. package/dist/cli/shell/sessions.js.map +1 -1
  121. package/dist/cli/shell/shell-metrics.d.ts +34 -0
  122. package/dist/cli/shell/shell-metrics.d.ts.map +1 -0
  123. package/dist/cli/shell/shell-metrics.js +98 -0
  124. package/dist/cli/shell/shell-metrics.js.map +1 -0
  125. package/dist/cli/shell/spawn.d.ts.map +1 -1
  126. package/dist/cli/shell/spawn.js +20 -6
  127. package/dist/cli/shell/spawn.js.map +1 -1
  128. package/dist/cli/shell/terminal.d.ts +26 -0
  129. package/dist/cli/shell/terminal.d.ts.map +1 -1
  130. package/dist/cli/shell/terminal.js +65 -2
  131. package/dist/cli/shell/terminal.js.map +1 -1
  132. package/dist/cli/shell/theme-colors.d.ts +39 -0
  133. package/dist/cli/shell/theme-colors.d.ts.map +1 -0
  134. package/dist/cli/shell/theme-colors.js +39 -0
  135. package/dist/cli/shell/theme-colors.js.map +1 -0
  136. package/dist/cli/shell/types.d.ts +2 -0
  137. package/dist/cli/shell/types.d.ts.map +1 -1
  138. package/dist/cli/shell/useAnimation.d.ts +42 -0
  139. package/dist/cli/shell/useAnimation.d.ts.map +1 -0
  140. package/dist/cli/shell/useAnimation.js +139 -0
  141. package/dist/cli/shell/useAnimation.js.map +1 -0
  142. package/dist/cli-entry.d.ts +0 -7
  143. package/dist/cli-entry.d.ts.map +1 -1
  144. package/dist/cli-entry.js +701 -96
  145. package/dist/cli-entry.js.map +1 -1
  146. package/package.json +18 -2
  147. package/templates/orchestration-log.md +1 -1
  148. package/templates/package.json +3 -0
  149. package/templates/ralph-triage.js +543 -0
  150. package/templates/scribe-charter.md +1 -1
  151. package/templates/squad.agent.md +10 -10
  152. package/templates/workflows/squad-heartbeat.yml +52 -196
  153. package/templates/workflows/squad-insider-release.yml +1 -1
@@ -22,7 +22,7 @@
22
22
 
23
23
  After every substantial work session:
24
24
 
25
- 1. **Log the session** to `.squad/log/{timestamp}-{topic}.md`:
25
+ 1. **Log the session** to `.squad/log/{timestamp}-{topic}.md` (use filename-safe timestamps — replace colons with hyphens, e.g., `2026-02-23T20-16-27Z` not `2026-02-23T20:16:27Z`, for Windows compatibility):
26
26
  - Who worked
27
27
  - What was done
28
28
  - Decisions made
@@ -717,8 +717,8 @@ prompt: |
717
717
  SPAWN MANIFEST: {spawn_manifest}
718
718
 
719
719
  Tasks (in order):
720
- 1. ORCHESTRATION LOG: Write .squad/orchestration-log/{timestamp}-{agent}.md per agent. Use ISO 8601 UTC timestamp.
721
- 2. SESSION LOG: Write .squad/log/{timestamp}-{topic}.md. Brief. Use ISO 8601 UTC timestamp.
720
+ 1. ORCHESTRATION LOG: Write .squad/orchestration-log/{timestamp}-{agent}.md per agent. Use filename-safe ISO 8601 UTC timestamp (replace colons with hyphens, e.g., `2026-02-23T20-16-27Z` not `2026-02-23T20:16:27Z`).
721
+ 2. SESSION LOG: Write .squad/log/{timestamp}-{topic}.md. Brief. Use filename-safe ISO 8601 UTC timestamp (replace colons with hyphens, e.g., `2026-02-23T20-16-27Z`).
722
722
  3. DECISION INBOX: Merge .squad/decisions/inbox/ → decisions.md, delete inbox files. Deduplicate.
723
723
  4. CROSS-AGENT: Append team updates to affected agents' history.md.
724
724
  5. DECISIONS ARCHIVE: If decisions.md exceeds ~20KB, archive entries older than 30 days to decisions-archive.md.
@@ -951,7 +951,7 @@ Ralph is a built-in squad member whose job is keeping tabs on work. **Ralph trac
951
951
 
952
952
  **⚡ CRITICAL BEHAVIOR: When Ralph is active, the coordinator MUST NOT stop and wait for user input between work items. Ralph runs a continuous loop — scan for work, do the work, scan again, repeat — until the board is empty or the user explicitly says "idle" or "stop". This is not optional. If work exists, keep going. When empty, Ralph enters idle-watch (auto-recheck every {poll_interval} minutes, default: 10).**
953
953
 
954
- **Between checks:** Ralph's in-session loop runs while work exists. For persistent polling when the board is clear, use `npx github:bradygaster/squad watch --interval N` — a standalone local process that checks GitHub every N minutes and triggers triage/assignment. See [Watch Mode](#watch-mode-squad-watch).
954
+ **Between checks:** Ralph's in-session loop runs while work exists. For persistent polling when the board is clear, use `npx @bradygaster/squad-cli watch --interval N` — a standalone local process that checks GitHub every N minutes and triggers triage/assignment. See [Watch Mode](#watch-mode-squad-watch).
955
955
 
956
956
  **On-demand reference:** Read `.squad/templates/ralph-reference.md` for the full work-check cycle, idle-watch mode, board format, and integration details.
957
957
 
@@ -1001,7 +1001,7 @@ gh pr list --state open --draft --json number,title,author,labels,checks --limit
1001
1001
  | **Review feedback** | PR has `CHANGES_REQUESTED` review | Route feedback to PR author agent to address |
1002
1002
  | **CI failures** | PR checks failing | Notify assigned agent to fix, or create a fix issue |
1003
1003
  | **Approved PRs** | PR approved, CI green, ready to merge | Merge and close related issue |
1004
- | **No work found** | All clear | Report: "📋 Board is clear. Ralph is idling." Suggest `npx github:bradygaster/squad watch` for persistent polling. |
1004
+ | **No work found** | All clear | Report: "📋 Board is clear. Ralph is idling." Suggest `npx @bradygaster/squad-cli watch` for persistent polling. |
1005
1005
 
1006
1006
  **Step 3 — Act on highest-priority item:**
1007
1007
  - Process one category at a time, highest priority first (untriaged > assigned > CI failures > review feedback > approved PRs)
@@ -1027,9 +1027,9 @@ After every 3-5 rounds, pause and report before continuing:
1027
1027
  Ralph's in-session loop processes work while it exists, then idles. For **persistent polling** between sessions or when you're away from the keyboard, use the `squad watch` CLI command:
1028
1028
 
1029
1029
  ```bash
1030
- npx github:bradygaster/squad watch # polls every 10 minutes (default)
1031
- npx github:bradygaster/squad watch --interval 5 # polls every 5 minutes
1032
- npx github:bradygaster/squad watch --interval 30 # polls every 30 minutes
1030
+ npx @bradygaster/squad-cli watch # polls every 10 minutes (default)
1031
+ npx @bradygaster/squad-cli watch --interval 5 # polls every 5 minutes
1032
+ npx @bradygaster/squad-cli watch --interval 30 # polls every 30 minutes
1033
1033
  ```
1034
1034
 
1035
1035
  This runs as a standalone local process (not inside Copilot) that:
@@ -1043,7 +1043,7 @@ This runs as a standalone local process (not inside Copilot) that:
1043
1043
  | Layer | When | How |
1044
1044
  |-------|------|-----|
1045
1045
  | **In-session** | You're at the keyboard | "Ralph, go" — active loop while work exists |
1046
- | **Local watchdog** | You're away but machine is on | `npx github:bradygaster/squad watch --interval 10` |
1046
+ | **Local watchdog** | You're away but machine is on | `npx @bradygaster/squad-cli watch --interval 10` |
1047
1047
  | **Cloud heartbeat** | Fully unattended | `squad-heartbeat.yml` GitHub Actions cron |
1048
1048
 
1049
1049
  ### Ralph State
@@ -1079,9 +1079,9 @@ After the coordinator's step 6 ("Immediately assess: Does anything trigger follo
1079
1079
  3. Follow-up work assessed → more agents if needed
1080
1080
  4. Ralph scans GitHub again (Step 1) → IMMEDIATELY, no pause
1081
1081
  5. More work found → repeat from step 2
1082
- 6. No more work → "📋 Board is clear. Ralph is idling." (suggest `npx github:bradygaster/squad watch` for persistent polling)
1082
+ 6. No more work → "📋 Board is clear. Ralph is idling." (suggest `npx @bradygaster/squad-cli watch` for persistent polling)
1083
1083
 
1084
- **Ralph does NOT ask "should I continue?" — Ralph KEEPS GOING.** Only stops on explicit "idle"/"stop" or session end. A clear board → idle-watch, not full stop. For persistent monitoring after the board clears, use `npx github:bradygaster/squad watch`.
1084
+ **Ralph does NOT ask "should I continue?" — Ralph KEEPS GOING.** Only stops on explicit "idle"/"stop" or session end. A clear board → idle-watch, not full stop. For persistent monitoring after the board clears, use `npx @bradygaster/squad-cli watch`.
1085
1085
 
1086
1086
  These are intent signals, not exact strings — match the user's meaning, not their exact words.
1087
1087
 
@@ -1,4 +1,10 @@
1
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.
2
8
 
3
9
  on:
4
10
  schedule:
@@ -25,225 +31,75 @@ jobs:
25
31
  steps:
26
32
  - uses: actions/checkout@v4
27
33
 
28
- - name: Ralph — Check for squad work
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') != ''
29
55
  uses: actions/github-script@v7
30
56
  with:
31
57
  script: |
32
58
  const fs = require('fs');
33
-
34
- // Read team roster — check .squad/ first, fall back to .ai-team/
35
- let teamFile = '.squad/team.md';
36
- if (!fs.existsSync(teamFile)) {
37
- teamFile = '.ai-team/team.md';
38
- }
39
- if (!fs.existsSync(teamFile)) {
40
- core.info('No .squad/team.md or .ai-team/team.md found — Ralph has nothing to monitor');
59
+ const path = 'triage-results.json';
60
+ if (!fs.existsSync(path)) {
61
+ core.info('No triage results — board is clear');
41
62
  return;
42
63
  }
43
-
44
- const content = fs.readFileSync(teamFile, 'utf8');
45
-
46
- // Check if Ralph is on the roster
47
- if (!content.includes('Ralph') || !content.includes('🔄')) {
48
- core.info('Ralph not on roster — heartbeat disabled');
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');
49
68
  return;
50
69
  }
51
-
52
- // Parse members from roster
53
- const lines = content.split('\n');
54
- const members = [];
55
- let inMembersTable = false;
56
- for (const line of lines) {
57
- if (line.match(/^##\s+(Members|Team Roster)/i)) {
58
- inMembersTable = true;
59
- continue;
60
- }
61
- if (inMembersTable && line.startsWith('## ')) break;
62
- if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) {
63
- const cells = line.split('|').map(c => c.trim()).filter(Boolean);
64
- if (cells.length >= 2 && !['Scribe', 'Ralph'].includes(cells[0])) {
65
- members.push({
66
- name: cells[0],
67
- role: cells[1],
68
- label: `squad:${cells[0].toLowerCase()}`
69
- });
70
- }
71
- }
72
- }
73
-
74
- if (members.length === 0) {
75
- core.info('No squad members found — nothing to monitor');
76
- return;
77
- }
78
-
79
- // 1. Find untriaged issues (labeled "squad" but no "squad:{member}" label)
80
- const { data: squadIssues } = await github.rest.issues.listForRepo({
81
- owner: context.repo.owner,
82
- repo: context.repo.repo,
83
- labels: 'squad',
84
- state: 'open',
85
- per_page: 20
86
- });
87
-
88
- const memberLabels = members.map(m => m.label);
89
- const untriaged = squadIssues.filter(issue => {
90
- const issueLabels = issue.labels.map(l => l.name);
91
- return !memberLabels.some(ml => issueLabels.includes(ml));
92
- });
93
-
94
- // 2. Find assigned but unstarted issues (has squad:{member} label, no assignee)
95
- const unstarted = [];
96
- for (const member of members) {
70
+
71
+ for (const decision of results) {
97
72
  try {
98
- const { data: memberIssues } = await github.rest.issues.listForRepo({
99
- owner: context.repo.owner,
100
- repo: context.repo.repo,
101
- labels: member.label,
102
- state: 'open',
103
- per_page: 10
104
- });
105
- for (const issue of memberIssues) {
106
- if (!issue.assignees || issue.assignees.length === 0) {
107
- unstarted.push({ issue, member });
108
- }
109
- }
110
- } catch (e) {
111
- // Label may not exist yet
112
- }
113
- }
114
-
115
- // 3. Find squad issues missing triage verdict (no go:* label)
116
- const missingVerdict = squadIssues.filter(issue => {
117
- const labels = issue.labels.map(l => l.name);
118
- return !labels.some(l => l.startsWith('go:'));
119
- });
120
-
121
- // 4. Find go:yes issues missing release target
122
- const goYesIssues = squadIssues.filter(issue => {
123
- const labels = issue.labels.map(l => l.name);
124
- return labels.includes('go:yes') && !labels.some(l => l.startsWith('release:'));
125
- });
126
-
127
- // 4b. Find issues missing type: label
128
- const missingType = squadIssues.filter(issue => {
129
- const labels = issue.labels.map(l => l.name);
130
- return !labels.some(l => l.startsWith('type:'));
131
- });
132
-
133
- // 5. Find open PRs that need attention
134
- const { data: openPRs } = await github.rest.pulls.list({
135
- owner: context.repo.owner,
136
- repo: context.repo.repo,
137
- state: 'open',
138
- per_page: 20
139
- });
140
-
141
- const squadPRs = openPRs.filter(pr =>
142
- pr.labels.some(l => l.name.startsWith('squad'))
143
- );
144
-
145
- // Build status summary
146
- const summary = [];
147
- if (untriaged.length > 0) {
148
- summary.push(`🔴 **${untriaged.length} untriaged issue(s)** need triage`);
149
- }
150
- if (unstarted.length > 0) {
151
- summary.push(`🟡 **${unstarted.length} assigned issue(s)** have no assignee`);
152
- }
153
- if (missingVerdict.length > 0) {
154
- summary.push(`⚪ **${missingVerdict.length} issue(s)** missing triage verdict (no \`go:\` label)`);
155
- }
156
- if (goYesIssues.length > 0) {
157
- summary.push(`⚪ **${goYesIssues.length} approved issue(s)** missing release target (no \`release:\` label)`);
158
- }
159
- if (missingType.length > 0) {
160
- summary.push(`⚪ **${missingType.length} issue(s)** missing \`type:\` label`);
161
- }
162
- if (squadPRs.length > 0) {
163
- const drafts = squadPRs.filter(pr => pr.draft).length;
164
- const ready = squadPRs.length - drafts;
165
- if (drafts > 0) summary.push(`🟡 **${drafts} draft PR(s)** in progress`);
166
- if (ready > 0) summary.push(`🟢 **${ready} PR(s)** open for review/merge`);
167
- }
168
-
169
- if (summary.length === 0) {
170
- core.info('📋 Board is clear — Ralph found no pending work');
171
- return;
172
- }
173
-
174
- core.info(`🔄 Ralph found work:\n${summary.join('\n')}`);
175
-
176
- // Auto-triage untriaged issues
177
- for (const issue of untriaged) {
178
- const issueText = `${issue.title}\n${issue.body || ''}`.toLowerCase();
179
- let assignedMember = null;
180
- let reason = '';
181
-
182
- // Simple keyword-based routing
183
- for (const member of members) {
184
- const role = member.role.toLowerCase();
185
- if ((role.includes('frontend') || role.includes('ui')) &&
186
- (issueText.includes('ui') || issueText.includes('frontend') ||
187
- issueText.includes('css') || issueText.includes('component'))) {
188
- assignedMember = member;
189
- reason = 'Matches frontend/UI domain';
190
- break;
191
- }
192
- if ((role.includes('backend') || role.includes('api') || role.includes('server')) &&
193
- (issueText.includes('api') || issueText.includes('backend') ||
194
- issueText.includes('database') || issueText.includes('endpoint'))) {
195
- assignedMember = member;
196
- reason = 'Matches backend/API domain';
197
- break;
198
- }
199
- if ((role.includes('test') || role.includes('qa')) &&
200
- (issueText.includes('test') || issueText.includes('bug') ||
201
- issueText.includes('fix') || issueText.includes('regression'))) {
202
- assignedMember = member;
203
- reason = 'Matches testing/QA domain';
204
- break;
205
- }
206
- }
207
-
208
- // Default to Lead
209
- if (!assignedMember) {
210
- const lead = members.find(m =>
211
- m.role.toLowerCase().includes('lead') ||
212
- m.role.toLowerCase().includes('architect')
213
- );
214
- if (lead) {
215
- assignedMember = lead;
216
- reason = 'No domain match — routed to Lead';
217
- }
218
- }
219
-
220
- if (assignedMember) {
221
- // Add member label
222
73
  await github.rest.issues.addLabels({
223
74
  owner: context.repo.owner,
224
75
  repo: context.repo.repo,
225
- issue_number: issue.number,
226
- labels: [assignedMember.label]
76
+ issue_number: decision.issueNumber,
77
+ labels: [decision.label]
227
78
  });
228
-
229
- // Post triage comment
79
+
230
80
  await github.rest.issues.createComment({
231
81
  owner: context.repo.owner,
232
82
  repo: context.repo.repo,
233
- issue_number: issue.number,
83
+ issue_number: decision.issueNumber,
234
84
  body: [
235
- `### 🔄 Ralph — Auto-Triage`,
85
+ '### 🔄 Ralph — Auto-Triage',
236
86
  '',
237
- `**Assigned to:** ${assignedMember.name} (${assignedMember.role})`,
238
- `**Reason:** ${reason}`,
87
+ `**Assigned to:** ${decision.assignTo}`,
88
+ `**Reason:** ${decision.reason}`,
89
+ `**Source:** ${decision.source}`,
239
90
  '',
240
- `> Ralph auto-triaged this issue via the squad heartbeat. To reassign, swap the \`squad:*\` label.`
91
+ '> Ralph auto-triaged this issue using routing rules.',
92
+ '> To reassign, swap the `squad:*` label.'
241
93
  ].join('\n')
242
94
  });
243
-
244
- core.info(`Auto-triaged #${issue.number} → ${assignedMember.name}`);
95
+
96
+ core.info(`Triaged #${decision.issueNumber} → ${decision.assignTo} (${decision.source})`);
97
+ } catch (e) {
98
+ core.warning(`Failed to triage #${decision.issueNumber}: ${e.message}`);
245
99
  }
246
100
  }
101
+
102
+ core.info(`🔄 Ralph triaged ${results.length} issue(s)`);
247
103
 
248
104
  # Copilot auto-assign step (uses PAT if available)
249
105
  - name: Ralph — Assign @copilot issues
@@ -50,7 +50,7 @@ jobs:
50
50
  run: |
51
51
  gh release create "${{ steps.version.outputs.insider_tag }}" \
52
52
  --title "${{ steps.version.outputs.insider_tag }}" \
53
- --notes "This is an insider/development build of Squad. Install with:\`\`\`bash\nnpx github:bradygaster/squad#${{ steps.version.outputs.insider_tag }}\n\`\`\`\n\n**Note:** Insider builds may be unstable and are intended for early adopters and testing only." \
53
+ --notes "This is an insider/development build of Squad. Install with:\`\`\`bash\nnpm install -g @bradygaster/squad-cli@${{ steps.version.outputs.insider_tag }}\n\`\`\`\n\n**Note:** Insider builds may be unstable and are intended for early adopters and testing only." \
54
54
  --prerelease
55
55
 
56
56
  - name: Verify release