@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.
- package/README.md +2 -2
- package/dist/cli/commands/copilot-bridge.d.ts +42 -0
- package/dist/cli/commands/copilot-bridge.d.ts.map +1 -0
- package/dist/cli/commands/copilot-bridge.js +191 -0
- package/dist/cli/commands/copilot-bridge.js.map +1 -0
- package/dist/cli/commands/import.js.map +1 -1
- package/dist/cli/commands/rc-tunnel.d.ts +30 -0
- package/dist/cli/commands/rc-tunnel.d.ts.map +1 -0
- package/dist/cli/commands/rc-tunnel.js +107 -0
- package/dist/cli/commands/rc-tunnel.js.map +1 -0
- package/dist/cli/commands/rc.d.ts +13 -0
- package/dist/cli/commands/rc.d.ts.map +1 -0
- package/dist/cli/commands/rc.js +270 -0
- package/dist/cli/commands/rc.js.map +1 -0
- package/dist/cli/commands/start.d.ts +18 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +219 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/upstream.js.map +1 -1
- package/dist/cli/commands/watch.d.ts +10 -0
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +181 -65
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/cli/core/cast.d.ts +40 -0
- package/dist/cli/core/cast.d.ts.map +1 -0
- package/dist/cli/core/cast.js +442 -0
- package/dist/cli/core/cast.js.map +1 -0
- package/dist/cli/core/gh-cli.d.ts +25 -0
- package/dist/cli/core/gh-cli.d.ts.map +1 -1
- package/dist/cli/core/gh-cli.js +15 -1
- package/dist/cli/core/gh-cli.js.map +1 -1
- package/dist/cli/core/init.d.ts +9 -1
- package/dist/cli/core/init.d.ts.map +1 -1
- package/dist/cli/core/init.js +108 -13
- package/dist/cli/core/init.js.map +1 -1
- package/dist/cli/core/migrations.js.map +1 -1
- package/dist/cli/core/nap.d.ts +37 -0
- package/dist/cli/core/nap.d.ts.map +1 -0
- package/dist/cli/core/nap.js +528 -0
- package/dist/cli/core/nap.js.map +1 -0
- package/dist/cli/core/output.d.ts +5 -0
- package/dist/cli/core/output.d.ts.map +1 -1
- package/dist/cli/core/output.js +7 -0
- package/dist/cli/core/output.js.map +1 -1
- package/dist/cli/core/upgrade.d.ts +0 -1
- package/dist/cli/core/upgrade.d.ts.map +1 -1
- package/dist/cli/core/upgrade.js.map +1 -1
- package/dist/cli/core/version.js.map +1 -1
- package/dist/cli/shell/agent-status.d.ts +11 -0
- package/dist/cli/shell/agent-status.d.ts.map +1 -0
- package/dist/cli/shell/agent-status.js +26 -0
- package/dist/cli/shell/agent-status.js.map +1 -0
- package/dist/cli/shell/commands.d.ts +10 -0
- package/dist/cli/shell/commands.d.ts.map +1 -1
- package/dist/cli/shell/commands.js +143 -29
- package/dist/cli/shell/commands.js.map +1 -1
- package/dist/cli/shell/components/AgentPanel.d.ts +1 -4
- package/dist/cli/shell/components/AgentPanel.d.ts.map +1 -1
- package/dist/cli/shell/components/AgentPanel.js +88 -6
- package/dist/cli/shell/components/AgentPanel.js.map +1 -1
- package/dist/cli/shell/components/App.d.ts +11 -6
- package/dist/cli/shell/components/App.d.ts.map +1 -1
- package/dist/cli/shell/components/App.js +212 -35
- package/dist/cli/shell/components/App.js.map +1 -1
- package/dist/cli/shell/components/ErrorBoundary.d.ts +22 -0
- package/dist/cli/shell/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/cli/shell/components/ErrorBoundary.js +31 -0
- package/dist/cli/shell/components/ErrorBoundary.js.map +1 -0
- package/dist/cli/shell/components/InputPrompt.d.ts +3 -0
- package/dist/cli/shell/components/InputPrompt.d.ts.map +1 -1
- package/dist/cli/shell/components/InputPrompt.js +155 -13
- package/dist/cli/shell/components/InputPrompt.js.map +1 -1
- package/dist/cli/shell/components/MessageStream.d.ts +17 -4
- package/dist/cli/shell/components/MessageStream.d.ts.map +1 -1
- package/dist/cli/shell/components/MessageStream.js +215 -23
- package/dist/cli/shell/components/MessageStream.js.map +1 -1
- package/dist/cli/shell/components/Separator.d.ts +17 -0
- package/dist/cli/shell/components/Separator.d.ts.map +1 -0
- package/dist/cli/shell/components/Separator.js +10 -0
- package/dist/cli/shell/components/Separator.js.map +1 -0
- package/dist/cli/shell/components/ThinkingIndicator.d.ts +21 -0
- package/dist/cli/shell/components/ThinkingIndicator.d.ts.map +1 -0
- package/dist/cli/shell/components/ThinkingIndicator.js +102 -0
- package/dist/cli/shell/components/ThinkingIndicator.js.map +1 -0
- package/dist/cli/shell/components/index.d.ts +3 -0
- package/dist/cli/shell/components/index.d.ts.map +1 -1
- package/dist/cli/shell/components/index.js +2 -0
- package/dist/cli/shell/components/index.js.map +1 -1
- package/dist/cli/shell/coordinator.d.ts +10 -0
- package/dist/cli/shell/coordinator.d.ts.map +1 -1
- package/dist/cli/shell/coordinator.js +99 -4
- package/dist/cli/shell/coordinator.js.map +1 -1
- package/dist/cli/shell/error-messages.d.ts +21 -0
- package/dist/cli/shell/error-messages.d.ts.map +1 -0
- package/dist/cli/shell/error-messages.js +61 -0
- package/dist/cli/shell/error-messages.js.map +1 -0
- package/dist/cli/shell/index.d.ts +24 -3
- package/dist/cli/shell/index.d.ts.map +1 -1
- package/dist/cli/shell/index.js +943 -34
- package/dist/cli/shell/index.js.map +1 -1
- package/dist/cli/shell/lifecycle.d.ts +2 -0
- package/dist/cli/shell/lifecycle.d.ts.map +1 -1
- package/dist/cli/shell/lifecycle.js +59 -6
- package/dist/cli/shell/lifecycle.js.map +1 -1
- package/dist/cli/shell/memory.d.ts +6 -1
- package/dist/cli/shell/memory.d.ts.map +1 -1
- package/dist/cli/shell/memory.js +12 -1
- package/dist/cli/shell/memory.js.map +1 -1
- package/dist/cli/shell/router.d.ts +16 -0
- package/dist/cli/shell/router.d.ts.map +1 -1
- package/dist/cli/shell/router.js +27 -0
- package/dist/cli/shell/router.js.map +1 -1
- package/dist/cli/shell/session-store.d.ts +47 -0
- package/dist/cli/shell/session-store.d.ts.map +1 -0
- package/dist/cli/shell/session-store.js +125 -0
- package/dist/cli/shell/session-store.js.map +1 -0
- package/dist/cli/shell/sessions.d.ts +2 -0
- package/dist/cli/shell/sessions.d.ts.map +1 -1
- package/dist/cli/shell/sessions.js +19 -5
- package/dist/cli/shell/sessions.js.map +1 -1
- package/dist/cli/shell/shell-metrics.d.ts +34 -0
- package/dist/cli/shell/shell-metrics.d.ts.map +1 -0
- package/dist/cli/shell/shell-metrics.js +98 -0
- package/dist/cli/shell/shell-metrics.js.map +1 -0
- package/dist/cli/shell/spawn.d.ts.map +1 -1
- package/dist/cli/shell/spawn.js +20 -6
- package/dist/cli/shell/spawn.js.map +1 -1
- package/dist/cli/shell/terminal.d.ts +26 -0
- package/dist/cli/shell/terminal.d.ts.map +1 -1
- package/dist/cli/shell/terminal.js +65 -2
- package/dist/cli/shell/terminal.js.map +1 -1
- package/dist/cli/shell/theme-colors.d.ts +39 -0
- package/dist/cli/shell/theme-colors.d.ts.map +1 -0
- package/dist/cli/shell/theme-colors.js +39 -0
- package/dist/cli/shell/theme-colors.js.map +1 -0
- package/dist/cli/shell/types.d.ts +2 -0
- package/dist/cli/shell/types.d.ts.map +1 -1
- package/dist/cli/shell/useAnimation.d.ts +42 -0
- package/dist/cli/shell/useAnimation.d.ts.map +1 -0
- package/dist/cli/shell/useAnimation.js +139 -0
- package/dist/cli/shell/useAnimation.js.map +1 -0
- package/dist/cli-entry.d.ts +0 -7
- package/dist/cli-entry.d.ts.map +1 -1
- package/dist/cli-entry.js +701 -96
- package/dist/cli-entry.js.map +1 -1
- package/package.json +18 -2
- package/templates/orchestration-log.md +1 -1
- package/templates/package.json +3 -0
- package/templates/ralph-triage.js +543 -0
- package/templates/scribe-charter.md +1 -1
- package/templates/squad.agent.md +10 -10
- package/templates/workflows/squad-heartbeat.yml +52 -196
- 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
|
package/templates/squad.agent.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
1031
|
-
npx
|
|
1032
|
-
npx
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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:
|
|
226
|
-
labels: [
|
|
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:
|
|
83
|
+
issue_number: decision.issueNumber,
|
|
234
84
|
body: [
|
|
235
|
-
|
|
85
|
+
'### 🔄 Ralph — Auto-Triage',
|
|
236
86
|
'',
|
|
237
|
-
`**Assigned to:** ${
|
|
238
|
-
`**Reason:** ${reason}`,
|
|
87
|
+
`**Assigned to:** ${decision.assignTo}`,
|
|
88
|
+
`**Reason:** ${decision.reason}`,
|
|
89
|
+
`**Source:** ${decision.source}`,
|
|
239
90
|
'',
|
|
240
|
-
|
|
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(`
|
|
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\
|
|
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
|