@sideboard-ai/core 0.1.9
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/LICENSE +190 -0
- package/dist/agents/cursor-runner.cjs +173 -0
- package/dist/agents/cursor-runner.d.cts +1 -0
- package/dist/agents/cursor-runner.d.ts +1 -0
- package/dist/agents/cursor-runner.js +102 -0
- package/dist/agents-OAX7XPKX.js +41 -0
- package/dist/app-settings-BDMLWCWI.js +61 -0
- package/dist/chunk-2M4OHXYX.js +198 -0
- package/dist/chunk-2R5VV4BA.js +143 -0
- package/dist/chunk-3DKGI32Q.js +92 -0
- package/dist/chunk-3WF3X46L.js +373 -0
- package/dist/chunk-AJ6ROGD7.js +74 -0
- package/dist/chunk-E4PWXO2C.js +4534 -0
- package/dist/chunk-HYRHI3QU.js +154 -0
- package/dist/chunk-ILQK4P5R.js +311 -0
- package/dist/chunk-LL7DTZ5B.js +1282 -0
- package/dist/chunk-M37RITA6.js +304 -0
- package/dist/chunk-TLJH3L2C.js +80 -0
- package/dist/chunk-WMCPLDW3.js +1413 -0
- package/dist/connected-teams-GF52Q7LB.js +22 -0
- package/dist/coordinator-prompt-6R2TX4WQ.js +22 -0
- package/dist/global-workspace-R44HGBU6.js +36 -0
- package/dist/index.cjs +9940 -0
- package/dist/index.d.cts +2284 -0
- package/dist/index.d.ts +2284 -0
- package/dist/index.js +929 -0
- package/dist/mcp/run-stdio.cjs +8654 -0
- package/dist/mcp/run-stdio.d.cts +2 -0
- package/dist/mcp/run-stdio.d.ts +2 -0
- package/dist/mcp/run-stdio.js +21 -0
- package/dist/paths-VPH3ITBK.js +26 -0
- package/dist/run-LF6E5IKL.js +10 -0
- package/dist/thread-store-UNPZNIFW.js +27 -0
- package/dist/title-4A2ATYNY.js +24 -0
- package/dist/workspaces-TCJFYI35.js +20 -0
- package/dist/worktree-NGFDN3J4.js +79 -0
- package/package.json +63 -0
|
@@ -0,0 +1,1282 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listThreads
|
|
3
|
+
} from "./chunk-HYRHI3QU.js";
|
|
4
|
+
import {
|
|
5
|
+
worktreesRoot
|
|
6
|
+
} from "./chunk-M37RITA6.js";
|
|
7
|
+
import {
|
|
8
|
+
gh,
|
|
9
|
+
git
|
|
10
|
+
} from "./chunk-AJ6ROGD7.js";
|
|
11
|
+
|
|
12
|
+
// src/git/worktree.ts
|
|
13
|
+
import { existsSync, readdirSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
|
|
16
|
+
// src/git/teams.ts
|
|
17
|
+
var FAMOUS_SOCCER_TEAMS = [
|
|
18
|
+
{ name: "Aalborg", slug: "aalborg" },
|
|
19
|
+
{ name: "Aalesund", slug: "aalesund" },
|
|
20
|
+
{ name: "Aarhus", slug: "aarhus" },
|
|
21
|
+
{ name: "Aberdeen", slug: "aberdeen" },
|
|
22
|
+
{ name: "AC Milan", slug: "ac-milan" },
|
|
23
|
+
{ name: "ADO Den Haag", slug: "ado-den-haag" },
|
|
24
|
+
{ name: "AIK", slug: "aik" },
|
|
25
|
+
{ name: "Ajaccio", slug: "ajaccio" },
|
|
26
|
+
{ name: "Ajax", slug: "ajax" },
|
|
27
|
+
{ name: "Al Ahly", slug: "al-ahly" },
|
|
28
|
+
{ name: "Al Hilal", slug: "al-hilal" },
|
|
29
|
+
{ name: "Alajuelense", slug: "alajuelense" },
|
|
30
|
+
{ name: "Almere City", slug: "almere-city" },
|
|
31
|
+
{ name: "Amiens", slug: "amiens" },
|
|
32
|
+
{ name: "Angers", slug: "angers" },
|
|
33
|
+
{ name: "Anyang", slug: "anyang" },
|
|
34
|
+
{ name: "Argentinos Juniors", slug: "argentinos-juniors" },
|
|
35
|
+
{ name: "Arsenal", slug: "arsenal" },
|
|
36
|
+
{ name: "Aston Villa", slug: "aston-villa" },
|
|
37
|
+
{ name: "Athletico Paranaense", slug: "athletico-paranaense" },
|
|
38
|
+
{ name: "Atlanta United", slug: "atlanta-united" },
|
|
39
|
+
{ name: "Atlas", slug: "atlas" },
|
|
40
|
+
{ name: "Atl\xE9tico Madrid", slug: "atletico-madrid" },
|
|
41
|
+
{ name: "Atl\xE9tico Mineiro", slug: "atletico-mineiro" },
|
|
42
|
+
{ name: "Atl\xE9tico San Luis", slug: "atletico-san-luis" },
|
|
43
|
+
{ name: "Augsburg", slug: "augsburg" },
|
|
44
|
+
{ name: "Austin FC", slug: "austin-fc" },
|
|
45
|
+
{ name: "Auxerre", slug: "auxerre" },
|
|
46
|
+
{ name: "Avispa Fukuoka", slug: "avispa-fukuoka" },
|
|
47
|
+
{ name: "AZ Alkmaar", slug: "az" },
|
|
48
|
+
{ name: "Bahia", slug: "bahia" },
|
|
49
|
+
{ name: "Banfield", slug: "banfield" },
|
|
50
|
+
{ name: "Barcelona", slug: "barcelona" },
|
|
51
|
+
{ name: "Bayer Leverkusen", slug: "bayer-leverkusen" },
|
|
52
|
+
{ name: "Bayern Munich", slug: "bayern-munich" },
|
|
53
|
+
{ name: "Belgrano", slug: "belgrano" },
|
|
54
|
+
{ name: "Benfica", slug: "benfica" },
|
|
55
|
+
{ name: "Birmingham Legion", slug: "birmingham-legion" },
|
|
56
|
+
{ name: "Boca Juniors", slug: "boca-juniors" },
|
|
57
|
+
{ name: "Bod\xF8/Glimt", slug: "bodo-glimt" },
|
|
58
|
+
{ name: "Bordeaux", slug: "bordeaux" },
|
|
59
|
+
{ name: "Borussia Dortmund", slug: "borussia-dortmund" },
|
|
60
|
+
{ name: "Botafogo", slug: "botafogo" },
|
|
61
|
+
{ name: "Bragantino", slug: "bragantino" },
|
|
62
|
+
{ name: "Brann", slug: "brann" },
|
|
63
|
+
{ name: "Brest", slug: "brest" },
|
|
64
|
+
{ name: "Brommapojkarna", slug: "brommapojkarna" },
|
|
65
|
+
{ name: "Br\xF8ndby", slug: "brondby" },
|
|
66
|
+
{ name: "Brooklyn FC", slug: "brooklyn-fc" },
|
|
67
|
+
{ name: "Bucheon", slug: "bucheon" },
|
|
68
|
+
{ name: "Caen", slug: "caen" },
|
|
69
|
+
{ name: "Cambuur", slug: "cambuur" },
|
|
70
|
+
{ name: "Carmelita", slug: "carmelita" },
|
|
71
|
+
{ name: "Cartagin\xE9s", slug: "cartagines" },
|
|
72
|
+
{ name: "Cear\xE1", slug: "ceara" },
|
|
73
|
+
{ name: "Celtic", slug: "celtic" },
|
|
74
|
+
{ name: "Central C\xF3rdoba", slug: "central-cordoba" },
|
|
75
|
+
{ name: "Cerezo Osaka", slug: "cerezo-osaka" },
|
|
76
|
+
{ name: "CF Montr\xE9al", slug: "cf-montreal" },
|
|
77
|
+
{ name: "Charleston Battery", slug: "charleston-battery" },
|
|
78
|
+
{ name: "Charlotte FC", slug: "charlotte-fc" },
|
|
79
|
+
{ name: "Chelsea", slug: "chelsea" },
|
|
80
|
+
{ name: "Chicago Fire", slug: "chicago-fire" },
|
|
81
|
+
{ name: "Chivas", slug: "chivas" },
|
|
82
|
+
{ name: "Clermont", slug: "clermont" },
|
|
83
|
+
{ name: "Club Am\xE9rica", slug: "club-america" },
|
|
84
|
+
{ name: "Cologne", slug: "cologne" },
|
|
85
|
+
{ name: "Colorado Rapids", slug: "colorado-rapids" },
|
|
86
|
+
{ name: "Columbus Crew", slug: "columbus-crew" },
|
|
87
|
+
{ name: "Corinthians", slug: "corinthians" },
|
|
88
|
+
{ name: "Cruz Azul", slug: "cruz-azul" },
|
|
89
|
+
{ name: "Cruzeiro", slug: "cruzeiro" },
|
|
90
|
+
{ name: "Daejeon", slug: "daejeon" },
|
|
91
|
+
{ name: "DC United", slug: "dc-united" },
|
|
92
|
+
{ name: "Defensa y Justicia", slug: "defensa-y-justicia" },
|
|
93
|
+
{ name: "Degerfors", slug: "degerfors" },
|
|
94
|
+
{ name: "Detroit City", slug: "detroit-city" },
|
|
95
|
+
{ name: "Dijon", slug: "dijon" },
|
|
96
|
+
{ name: "Djurg\xE5rden", slug: "djurgarden" },
|
|
97
|
+
{ name: "Dundee", slug: "dundee" },
|
|
98
|
+
{ name: "Dundee United", slug: "dundee-united" },
|
|
99
|
+
{ name: "Dynamo Kyiv", slug: "dynamo-kyiv" },
|
|
100
|
+
{ name: "Eintracht Frankfurt", slug: "eintracht-frankfurt" },
|
|
101
|
+
{ name: "El Paso Locomotive", slug: "el-paso-locomotive" },
|
|
102
|
+
{ name: "Elfsborg", slug: "elfsborg" },
|
|
103
|
+
{ name: "Emmen", slug: "emmen" },
|
|
104
|
+
{ name: "Esbjerg", slug: "esbjerg" },
|
|
105
|
+
{ name: "Estudiantes", slug: "estudiantes" },
|
|
106
|
+
{ name: "Everton", slug: "everton" },
|
|
107
|
+
{ name: "Excelsior", slug: "excelsior" },
|
|
108
|
+
{ name: "Fagiano Okayama", slug: "fagiano-okayama" },
|
|
109
|
+
{ name: "Falkirk", slug: "falkirk" },
|
|
110
|
+
{ name: "FC Cincinnati", slug: "fc-cincinnati" },
|
|
111
|
+
{ name: "FC Copenhagen", slug: "fc-copenhagen" },
|
|
112
|
+
{ name: "FC Dallas", slug: "fc-dallas" },
|
|
113
|
+
{ name: "FC Seoul", slug: "fc-seoul" },
|
|
114
|
+
{ name: "FC Tokyo", slug: "fc-tokyo" },
|
|
115
|
+
{ name: "FC Tulsa", slug: "fc-tulsa" },
|
|
116
|
+
{ name: "Feyenoord", slug: "feyenoord" },
|
|
117
|
+
{ name: "Flamengo", slug: "flamengo" },
|
|
118
|
+
{ name: "Fluminense", slug: "fluminense" },
|
|
119
|
+
{ name: "Fortaleza", slug: "fortaleza" },
|
|
120
|
+
{ name: "Fortuna Sittard", slug: "fortuna-sittard" },
|
|
121
|
+
{ name: "Fredrikstad", slug: "fredrikstad" },
|
|
122
|
+
{ name: "Freiburg", slug: "freiburg" },
|
|
123
|
+
{ name: "GAIS", slug: "gais" },
|
|
124
|
+
{ name: "Galatasaray", slug: "galatasaray" },
|
|
125
|
+
{ name: "Gamba Osaka", slug: "gamba-osaka" },
|
|
126
|
+
{ name: "Gangwon", slug: "gangwon" },
|
|
127
|
+
{ name: "Gimcheon Sangmu", slug: "gimcheon-sangmu" },
|
|
128
|
+
{ name: "Gimnasia", slug: "gimnasia" },
|
|
129
|
+
{ name: "Gladbach", slug: "gladbach" },
|
|
130
|
+
{ name: "Go Ahead Eagles", slug: "go-ahead-eagles" },
|
|
131
|
+
{ name: "Godoy Cruz", slug: "godoy-cruz" },
|
|
132
|
+
{ name: "Gr\xEAmio", slug: "gremio" },
|
|
133
|
+
{ name: "Groningen", slug: "groningen" },
|
|
134
|
+
{ name: "Guadalupe", slug: "guadalupe" },
|
|
135
|
+
{ name: "Guanacasteca", slug: "guanacasteca" },
|
|
136
|
+
{ name: "Guingamp", slug: "guingamp" },
|
|
137
|
+
{ name: "Gwangju", slug: "gwangju" },
|
|
138
|
+
{ name: "H\xE4cken", slug: "hacken" },
|
|
139
|
+
{ name: "Halmstad", slug: "halmstad" },
|
|
140
|
+
{ name: "Hamburg", slug: "hamburg" },
|
|
141
|
+
{ name: "Hammarby", slug: "hammarby" },
|
|
142
|
+
{ name: "Hartford Athletic", slug: "hartford-athletic" },
|
|
143
|
+
{ name: "Haugesund", slug: "haugesund" },
|
|
144
|
+
{ name: "Hearts", slug: "hearts" },
|
|
145
|
+
{ name: "Heerenveen", slug: "heerenveen" },
|
|
146
|
+
{ name: "Heidenheim", slug: "heidenheim" },
|
|
147
|
+
{ name: "Helsingborg", slug: "helsingborg" },
|
|
148
|
+
{ name: "Heracles", slug: "heracles" },
|
|
149
|
+
{ name: "Herediano", slug: "herediano" },
|
|
150
|
+
{ name: "Hibernian", slug: "hibernian" },
|
|
151
|
+
{ name: "Hoffenheim", slug: "hoffenheim" },
|
|
152
|
+
{ name: "Horsens", slug: "horsens" },
|
|
153
|
+
{ name: "Houston Dynamo", slug: "houston-dynamo" },
|
|
154
|
+
{ name: "Hurac\xE1n", slug: "huracan" },
|
|
155
|
+
{ name: "IFK G\xF6teborg", slug: "ifk-goteborg" },
|
|
156
|
+
{ name: "IFK Norrk\xF6ping", slug: "norrkoping" },
|
|
157
|
+
{ name: "Incheon United", slug: "incheon-united" },
|
|
158
|
+
{ name: "Independiente", slug: "independiente" },
|
|
159
|
+
{ name: "Indy Eleven", slug: "indy-eleven" },
|
|
160
|
+
{ name: "Instituto", slug: "instituto" },
|
|
161
|
+
{ name: "Inter Miami", slug: "inter-miami" },
|
|
162
|
+
{ name: "Inter Milan", slug: "inter-milan" },
|
|
163
|
+
{ name: "Internacional", slug: "internacional" },
|
|
164
|
+
{ name: "JEF United", slug: "jef-united" },
|
|
165
|
+
{ name: "Jeju SK", slug: "jeju-sk" },
|
|
166
|
+
{ name: "Jeonbuk", slug: "jeonbuk" },
|
|
167
|
+
{ name: "Ju\xE1rez", slug: "juarez" },
|
|
168
|
+
{ name: "Juventude", slug: "juventude" },
|
|
169
|
+
{ name: "Juventus", slug: "juventus" },
|
|
170
|
+
{ name: "Kalmar FF", slug: "kalmar" },
|
|
171
|
+
{ name: "Kashima Antlers", slug: "kashima-antlers" },
|
|
172
|
+
{ name: "Kashiwa Reysol", slug: "kashiwa-reysol" },
|
|
173
|
+
{ name: "Kawasaki Frontale", slug: "kawasaki-frontale" },
|
|
174
|
+
{ name: "KFUM Oslo", slug: "kfum-oslo" },
|
|
175
|
+
{ name: "Kilmarnock", slug: "kilmarnock" },
|
|
176
|
+
{ name: "Kristiansund", slug: "kristiansund" },
|
|
177
|
+
{ name: "Kyoto Sanga", slug: "kyoto-sanga" },
|
|
178
|
+
{ name: "LA Galaxy", slug: "la-galaxy" },
|
|
179
|
+
{ name: "LAFC", slug: "lafc" },
|
|
180
|
+
{ name: "Lan\xFAs", slug: "lanus" },
|
|
181
|
+
{ name: "Las Vegas Lights", slug: "las-vegas-lights" },
|
|
182
|
+
{ name: "Lazio", slug: "lazio" },
|
|
183
|
+
{ name: "Le Havre", slug: "le-havre" },
|
|
184
|
+
{ name: "Le Mans", slug: "le-mans" },
|
|
185
|
+
{ name: "Leeds United", slug: "leeds-united" },
|
|
186
|
+
{ name: "Leicester City", slug: "leicester-city" },
|
|
187
|
+
{ name: "Lens", slug: "lens" },
|
|
188
|
+
{ name: "Le\xF3n", slug: "leon" },
|
|
189
|
+
{ name: "Lexington SC", slug: "lexington-sc" },
|
|
190
|
+
{ name: "Liberia", slug: "liberia" },
|
|
191
|
+
{ name: "Lille", slug: "lille" },
|
|
192
|
+
{ name: "Lillestr\xF8m", slug: "lillestrom" },
|
|
193
|
+
{ name: "Lim\xF3n", slug: "limon" },
|
|
194
|
+
{ name: "Liverpool", slug: "liverpool" },
|
|
195
|
+
{ name: "Livingston", slug: "livingston" },
|
|
196
|
+
{ name: "Lorient", slug: "lorient" },
|
|
197
|
+
{ name: "Loudoun United", slug: "loudoun-united" },
|
|
198
|
+
{ name: "Louisville City", slug: "louisville-city" },
|
|
199
|
+
{ name: "Lyngby", slug: "lyngby" },
|
|
200
|
+
{ name: "Lyon", slug: "lyon" },
|
|
201
|
+
{ name: "Machida Zelvia", slug: "machida-zelvia" },
|
|
202
|
+
{ name: "Mainz", slug: "mainz" },
|
|
203
|
+
{ name: "Malm\xF6 FF", slug: "malmo" },
|
|
204
|
+
{ name: "Manchester City", slug: "manchester-city" },
|
|
205
|
+
{ name: "Manchester United", slug: "manchester-united" },
|
|
206
|
+
{ name: "Marseille", slug: "marseille" },
|
|
207
|
+
{ name: "Mazatl\xE1n", slug: "mazatlan" },
|
|
208
|
+
{ name: "Metz", slug: "metz" },
|
|
209
|
+
{ name: "Miami FC", slug: "miami-fc" },
|
|
210
|
+
{ name: "Midtjylland", slug: "midtjylland" },
|
|
211
|
+
{ name: "Minnesota United", slug: "minnesota-united" },
|
|
212
|
+
{ name: "Mirassol", slug: "mirassol" },
|
|
213
|
+
{ name: "Mito Hollyhock", slug: "mito-hollyhock" },
|
|
214
|
+
{ name: "Mj\xE4llby", slug: "mjallby" },
|
|
215
|
+
{ name: "Molde", slug: "molde" },
|
|
216
|
+
{ name: "Monaco", slug: "monaco" },
|
|
217
|
+
{ name: "Monterey Bay", slug: "monterey-bay" },
|
|
218
|
+
{ name: "Monterrey", slug: "monterrey" },
|
|
219
|
+
{ name: "Montpellier", slug: "montpellier" },
|
|
220
|
+
{ name: "Motherwell", slug: "motherwell" },
|
|
221
|
+
{ name: "Municipal Grecia", slug: "grecia" },
|
|
222
|
+
{ name: "NAC Breda", slug: "nac-breda" },
|
|
223
|
+
{ name: "Nagoya Grampus", slug: "nagoya-grampus" },
|
|
224
|
+
{ name: "Nancy", slug: "nancy" },
|
|
225
|
+
{ name: "Nantes", slug: "nantes" },
|
|
226
|
+
{ name: "Napoli", slug: "napoli" },
|
|
227
|
+
{ name: "Nashville SC", slug: "nashville-sc" },
|
|
228
|
+
{ name: "NEC Nijmegen", slug: "nec" },
|
|
229
|
+
{ name: "Necaxa", slug: "necaxa" },
|
|
230
|
+
{ name: "New England", slug: "new-england" },
|
|
231
|
+
{ name: "New Mexico United", slug: "new-mexico-united" },
|
|
232
|
+
{ name: "Newcastle", slug: "newcastle" },
|
|
233
|
+
{ name: "Newell's Old Boys", slug: "newells" },
|
|
234
|
+
{ name: "Nice", slug: "nice" },
|
|
235
|
+
{ name: "N\xEEmes", slug: "nimes" },
|
|
236
|
+
{ name: "Nordsj\xE6lland", slug: "nordsjaelland" },
|
|
237
|
+
{ name: "NY Red Bulls", slug: "ny-red-bulls" },
|
|
238
|
+
{ name: "NYCFC", slug: "nycfc" },
|
|
239
|
+
{ name: "Oakland Roots", slug: "oakland-roots" },
|
|
240
|
+
{ name: "OB Odense", slug: "ob-odense" },
|
|
241
|
+
{ name: "Odd", slug: "odd" },
|
|
242
|
+
{ name: "Olympiacos", slug: "olympiacos" },
|
|
243
|
+
{ name: "Orange County SC", slug: "orange-county-sc" },
|
|
244
|
+
{ name: "\xD6rebro", slug: "orebro" },
|
|
245
|
+
{ name: "Orlando City", slug: "orlando-city" },
|
|
246
|
+
{ name: "Pachuca", slug: "pachuca" },
|
|
247
|
+
{ name: "Palmeiras", slug: "palmeiras" },
|
|
248
|
+
{ name: "Paris FC", slug: "paris-fc" },
|
|
249
|
+
{ name: "Paris Saint-Germain", slug: "psg" },
|
|
250
|
+
{ name: "PEC Zwolle", slug: "pec-zwolle" },
|
|
251
|
+
{ name: "P\xE9rez Zeled\xF3n", slug: "perez-zeledon" },
|
|
252
|
+
{ name: "Philadelphia Union", slug: "philadelphia-union" },
|
|
253
|
+
{ name: "Phoenix Rising", slug: "phoenix-rising" },
|
|
254
|
+
{ name: "Pittsburgh Riverhounds", slug: "pittsburgh-riverhounds" },
|
|
255
|
+
{ name: "Platense", slug: "platense" },
|
|
256
|
+
{ name: "Pohang Steelers", slug: "pohang-steelers" },
|
|
257
|
+
{ name: "Portland Timbers", slug: "portland-timbers" },
|
|
258
|
+
{ name: "Porto", slug: "porto" },
|
|
259
|
+
{ name: "PSV", slug: "psv" },
|
|
260
|
+
{ name: "Puebla", slug: "puebla" },
|
|
261
|
+
{ name: "Pumas", slug: "pumas" },
|
|
262
|
+
{ name: "Puntarenas", slug: "puntarenas" },
|
|
263
|
+
{ name: "Quer\xE9taro", slug: "queretaro" },
|
|
264
|
+
{ name: "Racing", slug: "racing" },
|
|
265
|
+
{ name: "Randers", slug: "randers" },
|
|
266
|
+
{ name: "Rangers", slug: "rangers" },
|
|
267
|
+
{ name: "RB Leipzig", slug: "rb-leipzig" },
|
|
268
|
+
{ name: "Real Madrid", slug: "real-madrid" },
|
|
269
|
+
{ name: "Real Salt Lake", slug: "real-salt-lake" },
|
|
270
|
+
{ name: "Red Star Belgrade", slug: "red-star" },
|
|
271
|
+
{ name: "Reims", slug: "reims" },
|
|
272
|
+
{ name: "Rennes", slug: "rennes" },
|
|
273
|
+
{ name: "Rhode Island FC", slug: "rhode-island-fc" },
|
|
274
|
+
{ name: "River Plate", slug: "river-plate" },
|
|
275
|
+
{ name: "RKC Waalwijk", slug: "rkc" },
|
|
276
|
+
{ name: "Roma", slug: "roma" },
|
|
277
|
+
{ name: "Rosario Central", slug: "rosario-central" },
|
|
278
|
+
{ name: "Rosenborg", slug: "rosenborg" },
|
|
279
|
+
{ name: "Sacramento Republic", slug: "sacramento-republic" },
|
|
280
|
+
{ name: "Saint-\xC9tienne", slug: "saint-etienne" },
|
|
281
|
+
{ name: "San Antonio FC", slug: "san-antonio-fc" },
|
|
282
|
+
{ name: "San Carlos", slug: "san-carlos" },
|
|
283
|
+
{ name: "San Diego FC", slug: "san-diego-fc" },
|
|
284
|
+
{ name: "San Jose Earthquakes", slug: "san-jose-earthquakes" },
|
|
285
|
+
{ name: "San Lorenzo", slug: "san-lorenzo" },
|
|
286
|
+
{ name: "Sanfrecce Hiroshima", slug: "sanfrecce-hiroshima" },
|
|
287
|
+
{ name: "Santos", slug: "santos" },
|
|
288
|
+
{ name: "Santos de Gu\xE1piles", slug: "santos-guapiles" },
|
|
289
|
+
{ name: "Santos Laguna", slug: "santos-laguna" },
|
|
290
|
+
{ name: "S\xE3o Paulo", slug: "sao-paulo" },
|
|
291
|
+
{ name: "Saprissa", slug: "saprissa" },
|
|
292
|
+
{ name: "Sarpsborg", slug: "sarpsborg" },
|
|
293
|
+
{ name: "Schalke", slug: "schalke" },
|
|
294
|
+
{ name: "Seattle Sounders", slug: "seattle-sounders" },
|
|
295
|
+
{ name: "Sevilla", slug: "sevilla" },
|
|
296
|
+
{ name: "Shakhtar", slug: "shakhtar" },
|
|
297
|
+
{ name: "Shimizu S-Pulse", slug: "shimizu-s-pulse" },
|
|
298
|
+
{ name: "Silkeborg", slug: "silkeborg" },
|
|
299
|
+
{ name: "Sirius", slug: "sirius" },
|
|
300
|
+
{ name: "Sochaux", slug: "sochaux" },
|
|
301
|
+
{ name: "S\xF8nderjyske", slug: "sonderjyske" },
|
|
302
|
+
{ name: "Sparta Rotterdam", slug: "sparta-rotterdam" },
|
|
303
|
+
{ name: "Sport Recife", slug: "sport-recife" },
|
|
304
|
+
{ name: "Sporting", slug: "sporting" },
|
|
305
|
+
{ name: "Sporting Jax", slug: "sporting-jax" },
|
|
306
|
+
{ name: "Sporting KC", slug: "sporting-kc" },
|
|
307
|
+
{ name: "Sporting San Jos\xE9", slug: "sporting-san-jose" },
|
|
308
|
+
{ name: "St Mirren", slug: "st-mirren" },
|
|
309
|
+
{ name: "St. Louis City", slug: "st-louis-city" },
|
|
310
|
+
{ name: "St. Pauli", slug: "st-pauli" },
|
|
311
|
+
{ name: "Stab\xE6k", slug: "stabaek" },
|
|
312
|
+
{ name: "Strasbourg", slug: "strasbourg" },
|
|
313
|
+
{ name: "Str\xF8msgodset", slug: "stromsgodset" },
|
|
314
|
+
{ name: "Stuttgart", slug: "stuttgart" },
|
|
315
|
+
{ name: "Switchbacks", slug: "switchbacks" },
|
|
316
|
+
{ name: "Talleres", slug: "talleres" },
|
|
317
|
+
{ name: "Tampa Bay Rowdies", slug: "tampa-bay-rowdies" },
|
|
318
|
+
{ name: "Tigre", slug: "tigre" },
|
|
319
|
+
{ name: "Tigres", slug: "tigres" },
|
|
320
|
+
{ name: "Tijuana", slug: "tijuana" },
|
|
321
|
+
{ name: "Tokyo Verdy", slug: "tokyo-verdy" },
|
|
322
|
+
{ name: "Toluca", slug: "toluca" },
|
|
323
|
+
{ name: "Toronto FC", slug: "toronto-fc" },
|
|
324
|
+
{ name: "Tottenham", slug: "tottenham" },
|
|
325
|
+
{ name: "Toulouse", slug: "toulouse" },
|
|
326
|
+
{ name: "Troms\xF8", slug: "tromso" },
|
|
327
|
+
{ name: "Troyes", slug: "troyes" },
|
|
328
|
+
{ name: "Twente", slug: "twente" },
|
|
329
|
+
{ name: "Ulsan HD", slug: "ulsan-hd" },
|
|
330
|
+
{ name: "Uni\xF3n", slug: "union" },
|
|
331
|
+
{ name: "Union Berlin", slug: "union-berlin" },
|
|
332
|
+
{ name: "Urawa Reds", slug: "urawa-reds" },
|
|
333
|
+
{ name: "Utrecht", slug: "utrecht" },
|
|
334
|
+
{ name: "V-Varen Nagasaki", slug: "v-varen-nagasaki" },
|
|
335
|
+
{ name: "Valencia", slug: "valencia" },
|
|
336
|
+
{ name: "V\xE5lerenga", slug: "valerenga" },
|
|
337
|
+
{ name: "Vancouver Whitecaps", slug: "vancouver-whitecaps" },
|
|
338
|
+
{ name: "V\xE4rnamo", slug: "varnamo" },
|
|
339
|
+
{ name: "Vasco da Gama", slug: "vasco" },
|
|
340
|
+
{ name: "Vejle", slug: "vejle" },
|
|
341
|
+
{ name: "V\xE9lez Sarsfield", slug: "velez" },
|
|
342
|
+
{ name: "Viborg", slug: "viborg" },
|
|
343
|
+
{ name: "Viking", slug: "viking" },
|
|
344
|
+
{ name: "Vissel Kobe", slug: "vissel-kobe" },
|
|
345
|
+
{ name: "Vitesse", slug: "vitesse" },
|
|
346
|
+
{ name: "Vit\xF3ria", slug: "vitoria" },
|
|
347
|
+
{ name: "Volendam", slug: "volendam" },
|
|
348
|
+
{ name: "Werder Bremen", slug: "werder-bremen" },
|
|
349
|
+
{ name: "West Ham", slug: "west-ham" },
|
|
350
|
+
{ name: "Willem II", slug: "willem-ii" },
|
|
351
|
+
{ name: "Wolfsburg", slug: "wolfsburg" },
|
|
352
|
+
{ name: "Yokohama Marinos", slug: "yokohama-marinos" }
|
|
353
|
+
];
|
|
354
|
+
function teamNameFromSlug(slug) {
|
|
355
|
+
const normalized = slug.toLowerCase().replace(/^thread\//, "");
|
|
356
|
+
const suffixMatch = normalized.match(/^(.+)-(\d+)$/);
|
|
357
|
+
const base = suffixMatch?.[1] ?? normalized;
|
|
358
|
+
const suffix = suffixMatch?.[2];
|
|
359
|
+
const team = FAMOUS_SOCCER_TEAMS.find((t) => t.slug === base);
|
|
360
|
+
if (team) return suffix ? `${team.name} ${suffix}` : team.name;
|
|
361
|
+
return normalized.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
362
|
+
}
|
|
363
|
+
function teamSlugFromName(name) {
|
|
364
|
+
const trimmed = name.trim();
|
|
365
|
+
if (!trimmed) return null;
|
|
366
|
+
const suffixMatch = trimmed.match(/^(.+?)\s+(\d+)$/);
|
|
367
|
+
const baseName = suffixMatch?.[1] ?? trimmed;
|
|
368
|
+
const suffix = suffixMatch?.[2];
|
|
369
|
+
const team = FAMOUS_SOCCER_TEAMS.find(
|
|
370
|
+
(t) => t.name.toLowerCase() === baseName.toLowerCase()
|
|
371
|
+
);
|
|
372
|
+
if (!team) return null;
|
|
373
|
+
return suffix ? `${team.slug}-${suffix}` : team.slug;
|
|
374
|
+
}
|
|
375
|
+
function normalizeTakenSlug(raw) {
|
|
376
|
+
return raw.toLowerCase().replace(/^thread\//, "").replace(/\/+$/, "");
|
|
377
|
+
}
|
|
378
|
+
function takenSlugsFromThread(thread) {
|
|
379
|
+
const out = /* @__PURE__ */ new Set();
|
|
380
|
+
const titleSlug = thread.title ? teamSlugFromName(thread.title) : null;
|
|
381
|
+
if (titleSlug) out.add(normalizeTakenSlug(titleSlug));
|
|
382
|
+
if (thread.branchName?.trim()) {
|
|
383
|
+
const branch = normalizeTakenSlug(thread.branchName.trim());
|
|
384
|
+
if (branch) out.add(branch);
|
|
385
|
+
}
|
|
386
|
+
if (thread.worktreePath) {
|
|
387
|
+
const parts = thread.worktreePath.replace(/\/+$/, "").split("/");
|
|
388
|
+
const dir = parts[parts.length - 1];
|
|
389
|
+
if (dir) out.add(normalizeTakenSlug(dir));
|
|
390
|
+
}
|
|
391
|
+
return [...out];
|
|
392
|
+
}
|
|
393
|
+
function allocateTeamName(taken, random = Math.random) {
|
|
394
|
+
const takenSet = new Set(
|
|
395
|
+
[...taken].map((s) => normalizeTakenSlug(s)).filter(Boolean)
|
|
396
|
+
);
|
|
397
|
+
const available = FAMOUS_SOCCER_TEAMS.filter((t) => !takenSet.has(t.slug));
|
|
398
|
+
if (available.length > 0) {
|
|
399
|
+
const idx = Math.floor(random() * available.length);
|
|
400
|
+
return available[idx];
|
|
401
|
+
}
|
|
402
|
+
for (let n = 2; n < 1e3; n++) {
|
|
403
|
+
for (const t of FAMOUS_SOCCER_TEAMS) {
|
|
404
|
+
const slug = `${t.slug}-${n}`;
|
|
405
|
+
if (!takenSet.has(slug)) {
|
|
406
|
+
return { name: `${t.name} ${n}`, slug };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
throw new Error("No available soccer team names left");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/git/worktree-labels.ts
|
|
414
|
+
function normalizeWorktreePath(worktreePath) {
|
|
415
|
+
const trimmed = worktreePath.replace(/\/+$/, "") || "/";
|
|
416
|
+
const absolute = trimmed.startsWith("/");
|
|
417
|
+
const parts = trimmed.split("/").filter((p) => p && p !== ".");
|
|
418
|
+
const out = [];
|
|
419
|
+
for (const p of parts) {
|
|
420
|
+
if (p === "..") {
|
|
421
|
+
out.pop();
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
out.push(p);
|
|
425
|
+
}
|
|
426
|
+
return (absolute ? "/" : "") + out.join("/");
|
|
427
|
+
}
|
|
428
|
+
function worktreeNameFromPath(worktreePath) {
|
|
429
|
+
const normalized = worktreePath.replace(/\/+$/, "");
|
|
430
|
+
const parts = normalized.split("/");
|
|
431
|
+
return parts[parts.length - 1] || worktreePath;
|
|
432
|
+
}
|
|
433
|
+
function isPlaceholderBranch(branchName, worktreePath) {
|
|
434
|
+
const branch = branchName.trim();
|
|
435
|
+
if (!branch || branch === "HEAD") return true;
|
|
436
|
+
if (branch.startsWith("thread/")) return true;
|
|
437
|
+
const dir = worktreeNameFromPath(worktreePath).toLowerCase();
|
|
438
|
+
return branch.toLowerCase() === dir;
|
|
439
|
+
}
|
|
440
|
+
function branchDisplayLabel(branchName, worktreePath) {
|
|
441
|
+
const dir = worktreeNameFromPath(worktreePath);
|
|
442
|
+
if (isPlaceholderBranch(branchName, worktreePath)) {
|
|
443
|
+
const slug = branchName.replace(/^thread\//, "") || dir;
|
|
444
|
+
return teamNameFromSlug(slug);
|
|
445
|
+
}
|
|
446
|
+
return branchName.trim();
|
|
447
|
+
}
|
|
448
|
+
function threadDisplayLabel(thread) {
|
|
449
|
+
if (thread.userSetTitle && thread.title?.trim()) return thread.title.trim();
|
|
450
|
+
if (thread.prTitle?.trim()) return thread.prTitle.trim();
|
|
451
|
+
return branchDisplayLabel(thread.branchName, thread.worktreePath);
|
|
452
|
+
}
|
|
453
|
+
function worktreeDisplayLabel(thread) {
|
|
454
|
+
return threadDisplayLabel(thread);
|
|
455
|
+
}
|
|
456
|
+
function worktreeDisplayLabelForGroup(threads) {
|
|
457
|
+
if (threads.length === 0) return "Worktree";
|
|
458
|
+
const canonical = [...threads].sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
|
|
459
|
+
return threadDisplayLabel(canonical);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/git/pr-gates.ts
|
|
463
|
+
function buildMergeGateChecks(gate, opts = {}) {
|
|
464
|
+
const rows = [];
|
|
465
|
+
const base = gate.baseRefName?.trim() || "the base branch";
|
|
466
|
+
const link = gate.url?.trim() || null;
|
|
467
|
+
const mergeable = (gate.mergeable ?? "").toUpperCase();
|
|
468
|
+
const mergeState = (gate.mergeStateStatus ?? "").toUpperCase();
|
|
469
|
+
const review = (gate.reviewDecision ?? "").toUpperCase();
|
|
470
|
+
const suppressBlocked = opts.suppressGenericBlocked !== false;
|
|
471
|
+
const conflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
|
|
472
|
+
if (conflicting) {
|
|
473
|
+
rows.push({
|
|
474
|
+
name: "Merge conflicts",
|
|
475
|
+
state: mergeable === "CONFLICTING" ? "CONFLICTING" : mergeState || "DIRTY",
|
|
476
|
+
bucket: "fail",
|
|
477
|
+
startedAt: null,
|
|
478
|
+
completedAt: null,
|
|
479
|
+
link,
|
|
480
|
+
description: `This branch has conflicts that must be resolved before it can merge into ${base}.`,
|
|
481
|
+
workflow: "mergeability",
|
|
482
|
+
kind: "mergeability"
|
|
483
|
+
});
|
|
484
|
+
} else if (mergeState === "BEHIND") {
|
|
485
|
+
rows.push({
|
|
486
|
+
name: "Branch behind",
|
|
487
|
+
state: "BEHIND",
|
|
488
|
+
bucket: "fail",
|
|
489
|
+
startedAt: null,
|
|
490
|
+
completedAt: null,
|
|
491
|
+
link,
|
|
492
|
+
description: `Head is behind ${base}. Update this branch (merge or rebase) before merging.`,
|
|
493
|
+
workflow: "mergeability",
|
|
494
|
+
kind: "mergeability"
|
|
495
|
+
});
|
|
496
|
+
} else if (mergeState === "BLOCKED" && !suppressBlocked) {
|
|
497
|
+
rows.push({
|
|
498
|
+
name: "Merge blocked",
|
|
499
|
+
state: "BLOCKED",
|
|
500
|
+
bucket: "fail",
|
|
501
|
+
startedAt: null,
|
|
502
|
+
completedAt: null,
|
|
503
|
+
link,
|
|
504
|
+
description: "GitHub reports this PR as blocked (branch protection \u2014 required reviews and/or status checks).",
|
|
505
|
+
workflow: "mergeability",
|
|
506
|
+
kind: "mergeability"
|
|
507
|
+
});
|
|
508
|
+
} else if (mergeable === "UNKNOWN" || mergeState === "UNKNOWN") {
|
|
509
|
+
rows.push({
|
|
510
|
+
name: "Mergeability",
|
|
511
|
+
state: "UNKNOWN",
|
|
512
|
+
bucket: "pending",
|
|
513
|
+
startedAt: null,
|
|
514
|
+
completedAt: null,
|
|
515
|
+
link,
|
|
516
|
+
description: "GitHub is still computing whether this branch can merge.",
|
|
517
|
+
workflow: "mergeability",
|
|
518
|
+
kind: "mergeability"
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
if (review === "CHANGES_REQUESTED") {
|
|
522
|
+
rows.push({
|
|
523
|
+
name: "Code review",
|
|
524
|
+
state: "CHANGES_REQUESTED",
|
|
525
|
+
bucket: "fail",
|
|
526
|
+
startedAt: null,
|
|
527
|
+
completedAt: null,
|
|
528
|
+
link,
|
|
529
|
+
description: "A reviewer requested changes. Address their feedback before merging.",
|
|
530
|
+
workflow: "review",
|
|
531
|
+
kind: "review"
|
|
532
|
+
});
|
|
533
|
+
} else if (review === "REVIEW_REQUIRED") {
|
|
534
|
+
rows.push({
|
|
535
|
+
name: "Code review",
|
|
536
|
+
state: "REVIEW_REQUIRED",
|
|
537
|
+
bucket: "pending",
|
|
538
|
+
startedAt: null,
|
|
539
|
+
completedAt: null,
|
|
540
|
+
link,
|
|
541
|
+
description: "A required approving review is still outstanding.",
|
|
542
|
+
workflow: "review",
|
|
543
|
+
kind: "review"
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return rows;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/git/worktree.ts
|
|
550
|
+
function slugify(input) {
|
|
551
|
+
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
552
|
+
}
|
|
553
|
+
async function resolveRepoRoot(cwd) {
|
|
554
|
+
const { stdout } = await git(["rev-parse", "--show-toplevel"], cwd);
|
|
555
|
+
return stdout.trim();
|
|
556
|
+
}
|
|
557
|
+
function parseGithubSlugFromRemoteUrl(url) {
|
|
558
|
+
const trimmed = url.trim();
|
|
559
|
+
const match = trimmed.match(/github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/i) ?? trimmed.match(/github\.com[:/]([^/]+)\/([^/.]+)/i);
|
|
560
|
+
if (!match?.[1] || !match[2]) return null;
|
|
561
|
+
return `${match[1]}/${match[2]}`;
|
|
562
|
+
}
|
|
563
|
+
async function slugFromGitRemote(repoPath, remote) {
|
|
564
|
+
const result = await git(["remote", "get-url", remote], repoPath, {
|
|
565
|
+
reject: false
|
|
566
|
+
});
|
|
567
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return null;
|
|
568
|
+
return parseGithubSlugFromRemoteUrl(result.stdout);
|
|
569
|
+
}
|
|
570
|
+
async function resolveGithubRepoSlug(repoPath) {
|
|
571
|
+
const fromOrigin = await slugFromGitRemote(repoPath, "origin");
|
|
572
|
+
if (fromOrigin) return fromOrigin;
|
|
573
|
+
const viaGh = await gh(
|
|
574
|
+
["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"],
|
|
575
|
+
repoPath,
|
|
576
|
+
{ reject: false }
|
|
577
|
+
);
|
|
578
|
+
if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
|
|
579
|
+
return viaGh.stdout.trim();
|
|
580
|
+
}
|
|
581
|
+
for (const remote of ["upstream", "github"]) {
|
|
582
|
+
const slug = await slugFromGitRemote(repoPath, remote);
|
|
583
|
+
if (slug) return slug;
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
async function resolveDefaultBranch(repoPath) {
|
|
588
|
+
const viaGh = await gh(
|
|
589
|
+
["repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"],
|
|
590
|
+
repoPath,
|
|
591
|
+
{ reject: false }
|
|
592
|
+
);
|
|
593
|
+
if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
|
|
594
|
+
return viaGh.stdout.trim();
|
|
595
|
+
}
|
|
596
|
+
const viaOrigin = await git(
|
|
597
|
+
["symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
598
|
+
repoPath,
|
|
599
|
+
{ reject: false }
|
|
600
|
+
);
|
|
601
|
+
if (viaOrigin.exitCode === 0 && viaOrigin.stdout.trim()) {
|
|
602
|
+
return viaOrigin.stdout.trim().replace(/^refs\/remotes\/origin\//, "");
|
|
603
|
+
}
|
|
604
|
+
for (const candidate of ["main", "master"]) {
|
|
605
|
+
const check = await git(
|
|
606
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${candidate}`],
|
|
607
|
+
repoPath,
|
|
608
|
+
{ reject: false }
|
|
609
|
+
);
|
|
610
|
+
if (check.exitCode === 0) return candidate;
|
|
611
|
+
}
|
|
612
|
+
return "main";
|
|
613
|
+
}
|
|
614
|
+
async function resolveDiffBaseRef(cwd, branchOrRef, fallbackCwd) {
|
|
615
|
+
const ref = branchOrRef.trim();
|
|
616
|
+
if (!ref) return ref;
|
|
617
|
+
if (ref.startsWith("origin/") || ref.startsWith("refs/")) return ref;
|
|
618
|
+
if (ref.includes("/")) return ref;
|
|
619
|
+
const remote = `origin/${ref}`;
|
|
620
|
+
for (const dir of [cwd, fallbackCwd].filter(Boolean)) {
|
|
621
|
+
const ok = await git(["rev-parse", "--verify", remote], dir, { reject: false });
|
|
622
|
+
if (ok.exitCode === 0) return remote;
|
|
623
|
+
}
|
|
624
|
+
return ref;
|
|
625
|
+
}
|
|
626
|
+
async function listBranches(repoPath, opts) {
|
|
627
|
+
const { stdout } = await git(
|
|
628
|
+
["for-each-ref", "--format=%(refname:short)|%(HEAD)|%(upstream:short)", "refs/heads", "refs/remotes"],
|
|
629
|
+
repoPath
|
|
630
|
+
);
|
|
631
|
+
const seen = /* @__PURE__ */ new Set();
|
|
632
|
+
const branches = [];
|
|
633
|
+
for (const line of stdout.split("\n").filter(Boolean)) {
|
|
634
|
+
const [name, head] = line.split("|");
|
|
635
|
+
if (!name || name.endsWith("/HEAD")) continue;
|
|
636
|
+
const remote = name.startsWith("origin/");
|
|
637
|
+
const short = remote ? name.replace(/^origin\//, "") : name;
|
|
638
|
+
if (seen.has(short) && !remote) continue;
|
|
639
|
+
if (!seen.has(short)) {
|
|
640
|
+
seen.add(short);
|
|
641
|
+
branches.push({ name: short, remote, current: head === "*" });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const sorted = branches.sort((a, b) => a.name.localeCompare(b.name));
|
|
645
|
+
if (!opts?.unmergedOnly) return sorted;
|
|
646
|
+
const defaultBranch = await resolveDefaultBranch(repoPath);
|
|
647
|
+
const originDefault = `origin/${defaultBranch}`;
|
|
648
|
+
const originCheck = await git(
|
|
649
|
+
["rev-parse", "--verify", "--quiet", originDefault],
|
|
650
|
+
repoPath,
|
|
651
|
+
{ reject: false }
|
|
652
|
+
);
|
|
653
|
+
const mergeBase = originCheck.exitCode === 0 ? originDefault : defaultBranch;
|
|
654
|
+
const noMerged = await git(
|
|
655
|
+
[
|
|
656
|
+
"for-each-ref",
|
|
657
|
+
`--no-merged=${mergeBase}`,
|
|
658
|
+
"--format=%(refname:short)",
|
|
659
|
+
"refs/heads",
|
|
660
|
+
"refs/remotes/origin"
|
|
661
|
+
],
|
|
662
|
+
repoPath,
|
|
663
|
+
{ reject: false }
|
|
664
|
+
);
|
|
665
|
+
const keep = /* @__PURE__ */ new Set();
|
|
666
|
+
keep.add(defaultBranch);
|
|
667
|
+
for (const line of noMerged.stdout.split("\n").filter(Boolean)) {
|
|
668
|
+
const short = line.startsWith("origin/") ? line.replace(/^origin\//, "") : line;
|
|
669
|
+
if (short && short !== "HEAD") keep.add(short);
|
|
670
|
+
}
|
|
671
|
+
return sorted.filter((b) => keep.has(b.name));
|
|
672
|
+
}
|
|
673
|
+
async function listPrs(repoPath) {
|
|
674
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
675
|
+
const args = [
|
|
676
|
+
"pr",
|
|
677
|
+
"list",
|
|
678
|
+
"--json",
|
|
679
|
+
"number,title,headRefName,url,isCrossRepository",
|
|
680
|
+
"--limit",
|
|
681
|
+
"50"
|
|
682
|
+
];
|
|
683
|
+
if (slug) args.push("--repo", slug);
|
|
684
|
+
const { stdout, exitCode } = await gh(args, repoPath, { reject: false });
|
|
685
|
+
if (exitCode !== 0 || !stdout.trim()) return [];
|
|
686
|
+
return JSON.parse(stdout);
|
|
687
|
+
}
|
|
688
|
+
async function getPr(repoPath, number) {
|
|
689
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
690
|
+
const args = [
|
|
691
|
+
"pr",
|
|
692
|
+
"view",
|
|
693
|
+
String(number),
|
|
694
|
+
"--json",
|
|
695
|
+
"number,title,headRefName,url,isCrossRepository"
|
|
696
|
+
];
|
|
697
|
+
if (slug) args.push("--repo", slug);
|
|
698
|
+
const { stdout, exitCode } = await gh(args, repoPath, { reject: false });
|
|
699
|
+
if (exitCode !== 0 || !stdout.trim()) return null;
|
|
700
|
+
return JSON.parse(stdout);
|
|
701
|
+
}
|
|
702
|
+
function resolvePrSelector(thread) {
|
|
703
|
+
if (thread.prUrl?.trim()) return thread.prUrl.trim();
|
|
704
|
+
if (thread.sourceType === "pr" && thread.sourceRef?.trim()) {
|
|
705
|
+
return thread.sourceRef.replace(/^#/, "").trim();
|
|
706
|
+
}
|
|
707
|
+
if (thread.branchName?.trim()) return thread.branchName.trim();
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
function normalizeGhTime(value) {
|
|
711
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
712
|
+
if (value.startsWith("0001-01-01")) return null;
|
|
713
|
+
const ms = Date.parse(value);
|
|
714
|
+
return Number.isFinite(ms) ? value : null;
|
|
715
|
+
}
|
|
716
|
+
function normalizeCheck(raw) {
|
|
717
|
+
return {
|
|
718
|
+
name: String(raw.name ?? "check"),
|
|
719
|
+
state: String(raw.state ?? ""),
|
|
720
|
+
bucket: String(raw.bucket ?? "pending"),
|
|
721
|
+
startedAt: normalizeGhTime(raw.startedAt),
|
|
722
|
+
completedAt: normalizeGhTime(raw.completedAt),
|
|
723
|
+
link: typeof raw.link === "string" && raw.link ? raw.link : null,
|
|
724
|
+
description: typeof raw.description === "string" && raw.description ? raw.description : null,
|
|
725
|
+
workflow: typeof raw.workflow === "string" && raw.workflow ? raw.workflow : null,
|
|
726
|
+
kind: "ci"
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
async function fetchPrMergeGate(cwd, selector, slug) {
|
|
730
|
+
const args = [
|
|
731
|
+
"pr",
|
|
732
|
+
"view",
|
|
733
|
+
selector,
|
|
734
|
+
"--json",
|
|
735
|
+
"mergeable,mergeStateStatus,reviewDecision,baseRefName,url"
|
|
736
|
+
];
|
|
737
|
+
if (slug) args.push("--repo", slug);
|
|
738
|
+
const { stdout, exitCode, stderr } = await gh(args, cwd, { reject: false });
|
|
739
|
+
if (exitCode !== 0 || !stdout.trim()) {
|
|
740
|
+
if (/no pull requests found/i.test(stderr)) return null;
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
try {
|
|
744
|
+
const view = JSON.parse(stdout);
|
|
745
|
+
return {
|
|
746
|
+
mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
|
|
747
|
+
mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
|
|
748
|
+
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
749
|
+
baseRefName: typeof view.baseRefName === "string" && view.baseRefName ? view.baseRefName : null,
|
|
750
|
+
url: typeof view.url === "string" && view.url ? view.url : null
|
|
751
|
+
};
|
|
752
|
+
} catch {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async function detectLocalMergeConflicts(cwd, baseRefName) {
|
|
757
|
+
const baseName = (baseRefName?.trim() || await resolveDefaultBranch(cwd)).replace(
|
|
758
|
+
/^origin\//,
|
|
759
|
+
""
|
|
760
|
+
);
|
|
761
|
+
const baseRef = await resolveDiffBaseRef(cwd, baseName);
|
|
762
|
+
await git(["fetch", "origin", baseName], cwd, { reject: false });
|
|
763
|
+
const result = await git(
|
|
764
|
+
["merge-tree", "--write-tree", "--name-only", baseRef, "HEAD"],
|
|
765
|
+
cwd,
|
|
766
|
+
{ reject: false }
|
|
767
|
+
);
|
|
768
|
+
const out = `${result.stdout}
|
|
769
|
+
${result.stderr}`;
|
|
770
|
+
const conflicting = result.exitCode !== 0 || /\bCONFLICT\b/i.test(out);
|
|
771
|
+
const files = conflicting ? [
|
|
772
|
+
...new Set(
|
|
773
|
+
out.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("merge-tree:") && !/\bCONFLICT\b/i.test(l))
|
|
774
|
+
)
|
|
775
|
+
].slice(0, 20) : [];
|
|
776
|
+
return { conflicting, base: baseName, files };
|
|
777
|
+
}
|
|
778
|
+
async function getPrChecks(cwd, selector) {
|
|
779
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
780
|
+
const args = [
|
|
781
|
+
"pr",
|
|
782
|
+
"checks",
|
|
783
|
+
selector,
|
|
784
|
+
"--json",
|
|
785
|
+
"name,state,bucket,startedAt,completedAt,link,description,workflow"
|
|
786
|
+
];
|
|
787
|
+
if (slug) args.push("--repo", slug);
|
|
788
|
+
const { stdout, exitCode, stderr } = await gh(args, cwd, { reject: false });
|
|
789
|
+
const errText = stderr.trim();
|
|
790
|
+
let ciChecks;
|
|
791
|
+
if (!stdout.trim()) {
|
|
792
|
+
if (/no pull requests found/i.test(errText)) return null;
|
|
793
|
+
if (/auth|login|HTTP\s*401|credentials|token/i.test(errText)) {
|
|
794
|
+
throw new Error(errText);
|
|
795
|
+
}
|
|
796
|
+
if (exitCode === 0 || exitCode === 1 || exitCode === 8) {
|
|
797
|
+
if (exitCode === 1 && errText && !/fail|check/i.test(errText)) {
|
|
798
|
+
throw new Error(errText);
|
|
799
|
+
}
|
|
800
|
+
ciChecks = [];
|
|
801
|
+
} else {
|
|
802
|
+
throw new Error(errText || `gh pr checks failed (${exitCode})`);
|
|
803
|
+
}
|
|
804
|
+
} else {
|
|
805
|
+
try {
|
|
806
|
+
const parsed = JSON.parse(stdout);
|
|
807
|
+
ciChecks = parsed.map(normalizeCheck);
|
|
808
|
+
} catch {
|
|
809
|
+
throw new Error(errText || "gh pr checks returned invalid JSON");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
let gate = await fetchPrMergeGate(cwd, selector, slug);
|
|
813
|
+
const mergeable = (gate?.mergeable ?? "").toUpperCase();
|
|
814
|
+
const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
|
|
815
|
+
const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
|
|
816
|
+
const needsLocalProbe = !alreadyConflicting;
|
|
817
|
+
if (needsLocalProbe) {
|
|
818
|
+
try {
|
|
819
|
+
const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
|
|
820
|
+
if (local.conflicting) {
|
|
821
|
+
const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
|
|
822
|
+
gate = {
|
|
823
|
+
mergeable: "CONFLICTING",
|
|
824
|
+
mergeStateStatus: "DIRTY",
|
|
825
|
+
reviewDecision: gate?.reviewDecision ?? null,
|
|
826
|
+
baseRefName: local.base,
|
|
827
|
+
url: gate?.url ?? null
|
|
828
|
+
};
|
|
829
|
+
const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
|
|
830
|
+
const review2 = (gate.reviewDecision ?? "").toUpperCase();
|
|
831
|
+
const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
|
|
832
|
+
const gateRows2 = buildMergeGateChecks(gate, {
|
|
833
|
+
suppressGenericBlocked: ciFailed2 || hasReviewRow2
|
|
834
|
+
}).map(
|
|
835
|
+
(row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
|
|
836
|
+
...row,
|
|
837
|
+
description: `${row.description ?? ""}${fileHint}`.trim()
|
|
838
|
+
} : row
|
|
839
|
+
);
|
|
840
|
+
return [...gateRows2, ...ciChecks];
|
|
841
|
+
}
|
|
842
|
+
if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
|
|
843
|
+
gate = {
|
|
844
|
+
...gate,
|
|
845
|
+
mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
|
|
846
|
+
mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (!gate) {
|
|
853
|
+
return ciChecks;
|
|
854
|
+
}
|
|
855
|
+
const ciFailed = ciChecks.some((c) => c.bucket === "fail");
|
|
856
|
+
const review = (gate.reviewDecision ?? "").toUpperCase();
|
|
857
|
+
const hasReviewRow = review === "CHANGES_REQUESTED" || review === "REVIEW_REQUIRED";
|
|
858
|
+
const gateRows = buildMergeGateChecks(gate, {
|
|
859
|
+
// Avoid duplicating "blocked" when CI failures or review rows already explain it.
|
|
860
|
+
suppressGenericBlocked: ciFailed || hasReviewRow
|
|
861
|
+
});
|
|
862
|
+
return [...gateRows, ...ciChecks];
|
|
863
|
+
}
|
|
864
|
+
async function getPrDetails(cwd, selector) {
|
|
865
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
866
|
+
const viewArgs = [
|
|
867
|
+
"pr",
|
|
868
|
+
"view",
|
|
869
|
+
selector,
|
|
870
|
+
"--json",
|
|
871
|
+
[
|
|
872
|
+
"number",
|
|
873
|
+
"title",
|
|
874
|
+
"body",
|
|
875
|
+
"url",
|
|
876
|
+
"state",
|
|
877
|
+
"isDraft",
|
|
878
|
+
"reviewDecision",
|
|
879
|
+
"author",
|
|
880
|
+
"baseRefName",
|
|
881
|
+
"headRefName",
|
|
882
|
+
"additions",
|
|
883
|
+
"deletions",
|
|
884
|
+
"changedFiles",
|
|
885
|
+
"commits",
|
|
886
|
+
"comments",
|
|
887
|
+
"reviews"
|
|
888
|
+
].join(",")
|
|
889
|
+
];
|
|
890
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
891
|
+
const { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
|
|
892
|
+
if (exitCode !== 0 || !stdout.trim()) {
|
|
893
|
+
if (stderr.trim()) {
|
|
894
|
+
if (/no pull requests found/i.test(stderr)) return null;
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
let view;
|
|
899
|
+
try {
|
|
900
|
+
view = JSON.parse(stdout);
|
|
901
|
+
} catch {
|
|
902
|
+
throw new Error(stderr.trim() || "gh pr view returned invalid JSON");
|
|
903
|
+
}
|
|
904
|
+
let checks = [];
|
|
905
|
+
try {
|
|
906
|
+
checks = await getPrChecks(cwd, selector) ?? [];
|
|
907
|
+
} catch {
|
|
908
|
+
checks = [];
|
|
909
|
+
}
|
|
910
|
+
const author = view.author ?? {};
|
|
911
|
+
const commits = Array.isArray(view.commits) ? view.commits : [];
|
|
912
|
+
const comments = Array.isArray(view.comments) ? view.comments : [];
|
|
913
|
+
const reviews = Array.isArray(view.reviews) ? view.reviews : [];
|
|
914
|
+
return {
|
|
915
|
+
number: Number(view.number),
|
|
916
|
+
title: String(view.title ?? ""),
|
|
917
|
+
body: String(view.body ?? ""),
|
|
918
|
+
url: String(view.url ?? ""),
|
|
919
|
+
state: String(view.state ?? ""),
|
|
920
|
+
isDraft: Boolean(view.isDraft),
|
|
921
|
+
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
922
|
+
author: { login: author.login ?? "unknown", name: author.name ?? null },
|
|
923
|
+
baseRefName: String(view.baseRefName ?? ""),
|
|
924
|
+
headRefName: String(view.headRefName ?? ""),
|
|
925
|
+
additions: Number(view.additions ?? 0),
|
|
926
|
+
deletions: Number(view.deletions ?? 0),
|
|
927
|
+
changedFiles: Number(view.changedFiles ?? 0),
|
|
928
|
+
commits: commits.map((c) => {
|
|
929
|
+
const row = c;
|
|
930
|
+
const authors = Array.isArray(row.authors) ? row.authors.map((a) => {
|
|
931
|
+
const actor = a;
|
|
932
|
+
return { login: actor.login ?? "unknown", name: actor.name ?? null };
|
|
933
|
+
}) : [];
|
|
934
|
+
return {
|
|
935
|
+
oid: String(row.oid ?? ""),
|
|
936
|
+
messageHeadline: String(row.messageHeadline ?? ""),
|
|
937
|
+
committedDate: String(row.committedDate ?? ""),
|
|
938
|
+
authors
|
|
939
|
+
};
|
|
940
|
+
}),
|
|
941
|
+
comments: comments.map((c) => {
|
|
942
|
+
const row = c;
|
|
943
|
+
const a = row.author ?? {};
|
|
944
|
+
return {
|
|
945
|
+
author: { login: a.login ?? "unknown" },
|
|
946
|
+
body: String(row.body ?? ""),
|
|
947
|
+
createdAt: String(row.createdAt ?? "")
|
|
948
|
+
};
|
|
949
|
+
}),
|
|
950
|
+
reviews: reviews.map((r) => {
|
|
951
|
+
const row = r;
|
|
952
|
+
const a = row.author ?? {};
|
|
953
|
+
return {
|
|
954
|
+
author: { login: a.login ?? "unknown" },
|
|
955
|
+
state: String(row.state ?? ""),
|
|
956
|
+
body: String(row.body ?? ""),
|
|
957
|
+
submittedAt: normalizeGhTime(row.submittedAt)
|
|
958
|
+
};
|
|
959
|
+
}),
|
|
960
|
+
checks
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
async function fetchPrHead(repoPath, number, localBranch) {
|
|
964
|
+
await git(
|
|
965
|
+
["fetch", "origin", `pull/${number}/head:${localBranch}`],
|
|
966
|
+
repoPath,
|
|
967
|
+
{ reject: false }
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
async function resolveWorktreeStartPoint(repoPath, sourceRef) {
|
|
971
|
+
const ref = sourceRef.trim();
|
|
972
|
+
if (!ref) throw new Error("sourceRef is required");
|
|
973
|
+
if (ref.startsWith("origin/") || ref.startsWith("refs/")) {
|
|
974
|
+
const ok = await git(["rev-parse", "--verify", ref], repoPath, {
|
|
975
|
+
reject: false
|
|
976
|
+
});
|
|
977
|
+
if (ok.exitCode === 0) return ref;
|
|
978
|
+
}
|
|
979
|
+
const remote = `origin/${ref}`;
|
|
980
|
+
const remoteOk = await git(["rev-parse", "--verify", remote], repoPath, {
|
|
981
|
+
reject: false
|
|
982
|
+
});
|
|
983
|
+
if (remoteOk.exitCode === 0) return remote;
|
|
984
|
+
const localOk = await git(["rev-parse", "--verify", ref], repoPath, {
|
|
985
|
+
reject: false
|
|
986
|
+
});
|
|
987
|
+
if (localOk.exitCode === 0) return ref;
|
|
988
|
+
return ref;
|
|
989
|
+
}
|
|
990
|
+
async function createThreadWorktree(opts) {
|
|
991
|
+
let branchName = `thread/${opts.slug}`;
|
|
992
|
+
const worktreePath = join(worktreesRoot(opts.repoPath), opts.slug);
|
|
993
|
+
if (existsSync(worktreePath)) {
|
|
994
|
+
throw new Error(`Worktree already exists at ${worktreePath}`);
|
|
995
|
+
}
|
|
996
|
+
await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
|
|
997
|
+
if (!opts.sourceRef.startsWith("origin/") && !opts.sourceRef.startsWith("refs/")) {
|
|
998
|
+
await git(["fetch", "origin", opts.sourceRef], opts.repoPath, {
|
|
999
|
+
reject: false
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
const startPoint = await resolveWorktreeStartPoint(
|
|
1003
|
+
opts.repoPath,
|
|
1004
|
+
opts.sourceRef
|
|
1005
|
+
);
|
|
1006
|
+
const add = await git(
|
|
1007
|
+
["worktree", "add", "-b", branchName, worktreePath, startPoint],
|
|
1008
|
+
opts.repoPath,
|
|
1009
|
+
{ reject: false }
|
|
1010
|
+
);
|
|
1011
|
+
if (add.exitCode !== 0) {
|
|
1012
|
+
const err = `${add.stderr}
|
|
1013
|
+
${add.stdout}`;
|
|
1014
|
+
if (/already used by worktree|already exists|checked out/i.test(err)) {
|
|
1015
|
+
for (let n = 2; n <= 20; n++) {
|
|
1016
|
+
const alt = `${branchName}-${n}`;
|
|
1017
|
+
const retry = await git(
|
|
1018
|
+
["worktree", "add", "-b", alt, worktreePath, startPoint],
|
|
1019
|
+
opts.repoPath,
|
|
1020
|
+
{ reject: false }
|
|
1021
|
+
);
|
|
1022
|
+
if (retry.exitCode === 0) {
|
|
1023
|
+
branchName = alt;
|
|
1024
|
+
return { branchName, worktreePath };
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
throw new Error(
|
|
1029
|
+
`Failed to create worktree: ${add.stderr.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
return { branchName, worktreePath };
|
|
1033
|
+
}
|
|
1034
|
+
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
1035
|
+
await git(["worktree", "remove", "--force", worktreePath], repoPath, {
|
|
1036
|
+
reject: false
|
|
1037
|
+
});
|
|
1038
|
+
if (opts?.deleteBranch) {
|
|
1039
|
+
await git(["branch", "-D", opts.deleteBranch], repoPath, { reject: false });
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
async function listWorktrees(repoPath) {
|
|
1043
|
+
const { stdout } = await git(
|
|
1044
|
+
["worktree", "list", "--porcelain"],
|
|
1045
|
+
repoPath,
|
|
1046
|
+
{ reject: false }
|
|
1047
|
+
);
|
|
1048
|
+
if (!stdout.trim()) return [];
|
|
1049
|
+
const entries = [];
|
|
1050
|
+
let current = null;
|
|
1051
|
+
for (const line of stdout.split("\n")) {
|
|
1052
|
+
if (line.startsWith("worktree ")) {
|
|
1053
|
+
if (current) entries.push(current);
|
|
1054
|
+
current = { path: line.slice("worktree ".length), branch: null };
|
|
1055
|
+
} else if (line.startsWith("branch ") && current) {
|
|
1056
|
+
current.branch = line.slice("branch ".length).replace(/^refs\/heads\//, "");
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (current) entries.push(current);
|
|
1060
|
+
return entries;
|
|
1061
|
+
}
|
|
1062
|
+
async function isDirty(worktreePath) {
|
|
1063
|
+
const { stdout } = await git(["status", "--porcelain"], worktreePath);
|
|
1064
|
+
return stdout.trim().length > 0;
|
|
1065
|
+
}
|
|
1066
|
+
async function currentBranch(worktreePath) {
|
|
1067
|
+
const { stdout } = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
|
|
1068
|
+
return stdout.trim();
|
|
1069
|
+
}
|
|
1070
|
+
async function commitAll(worktreePath, message) {
|
|
1071
|
+
const dirty = await isDirty(worktreePath);
|
|
1072
|
+
if (!dirty) return false;
|
|
1073
|
+
await git(["add", "-A"], worktreePath);
|
|
1074
|
+
await git(["commit", "-m", message], worktreePath);
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
async function pushBranch(worktreePath, branchName) {
|
|
1078
|
+
await git(["push", "-u", "origin", branchName], worktreePath);
|
|
1079
|
+
}
|
|
1080
|
+
async function mergePr(cwd, selector, opts) {
|
|
1081
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1082
|
+
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
|
|
1083
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
1084
|
+
const before = await gh(viewArgs, cwd, { reject: false });
|
|
1085
|
+
if (before.exitCode !== 0 || !before.stdout.trim()) {
|
|
1086
|
+
throw new Error(before.stderr.trim() || "Could not load pull request");
|
|
1087
|
+
}
|
|
1088
|
+
let url = "";
|
|
1089
|
+
let isDraft = false;
|
|
1090
|
+
try {
|
|
1091
|
+
const parsed = JSON.parse(before.stdout);
|
|
1092
|
+
url = String(parsed.url ?? "");
|
|
1093
|
+
isDraft = Boolean(parsed.isDraft);
|
|
1094
|
+
if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
|
|
1095
|
+
return { url, state: "MERGED" };
|
|
1096
|
+
}
|
|
1097
|
+
} catch {
|
|
1098
|
+
throw new Error("Could not parse pull request details");
|
|
1099
|
+
}
|
|
1100
|
+
if (isDraft) {
|
|
1101
|
+
const readyArgs = ["pr", "ready", selector];
|
|
1102
|
+
if (slug) readyArgs.push("--repo", slug);
|
|
1103
|
+
const ready = await gh(readyArgs, cwd, { reject: false });
|
|
1104
|
+
if (ready.exitCode !== 0) {
|
|
1105
|
+
throw new Error(
|
|
1106
|
+
ready.stderr.trim() || ready.stdout.trim() || "Could not mark draft pull request as ready"
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const method = opts?.method ?? "squash";
|
|
1111
|
+
const mergeFlag = method === "rebase" ? "--rebase" : method === "merge" ? "--merge" : "--squash";
|
|
1112
|
+
const args = ["pr", "merge", selector, mergeFlag, "--delete-branch=false"];
|
|
1113
|
+
if (slug) args.push("--repo", slug);
|
|
1114
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1115
|
+
if (exitCode !== 0) {
|
|
1116
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
|
|
1117
|
+
}
|
|
1118
|
+
const after = await gh(viewArgs, cwd, { reject: false });
|
|
1119
|
+
if (after.exitCode === 0 && after.stdout.trim()) {
|
|
1120
|
+
try {
|
|
1121
|
+
const parsed = JSON.parse(after.stdout);
|
|
1122
|
+
return {
|
|
1123
|
+
url: String(parsed.url ?? url),
|
|
1124
|
+
state: String(parsed.state ?? "MERGED")
|
|
1125
|
+
};
|
|
1126
|
+
} catch {
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return { url, state: "MERGED" };
|
|
1130
|
+
}
|
|
1131
|
+
async function createOrUpdatePr(worktreePath, opts) {
|
|
1132
|
+
const existing = await gh(
|
|
1133
|
+
["pr", "view", opts.head, "--json", "url", "--jq", ".url"],
|
|
1134
|
+
worktreePath,
|
|
1135
|
+
{ reject: false }
|
|
1136
|
+
);
|
|
1137
|
+
if (existing.exitCode === 0 && existing.stdout.trim() && !opts.web) {
|
|
1138
|
+
const url2 = existing.stdout.trim();
|
|
1139
|
+
await gh(
|
|
1140
|
+
[
|
|
1141
|
+
"pr",
|
|
1142
|
+
"edit",
|
|
1143
|
+
opts.head,
|
|
1144
|
+
"--title",
|
|
1145
|
+
opts.title,
|
|
1146
|
+
"--body",
|
|
1147
|
+
opts.body ?? opts.title
|
|
1148
|
+
],
|
|
1149
|
+
worktreePath,
|
|
1150
|
+
{ reject: false }
|
|
1151
|
+
);
|
|
1152
|
+
return url2;
|
|
1153
|
+
}
|
|
1154
|
+
if (opts.web) {
|
|
1155
|
+
const args2 = [
|
|
1156
|
+
"pr",
|
|
1157
|
+
"create",
|
|
1158
|
+
"--web",
|
|
1159
|
+
"--title",
|
|
1160
|
+
opts.title,
|
|
1161
|
+
"--body",
|
|
1162
|
+
opts.body ?? opts.title,
|
|
1163
|
+
"--base",
|
|
1164
|
+
opts.base,
|
|
1165
|
+
"--head",
|
|
1166
|
+
opts.head
|
|
1167
|
+
];
|
|
1168
|
+
if (opts.draft) args2.push("--draft");
|
|
1169
|
+
await gh(args2, worktreePath, { reject: false });
|
|
1170
|
+
const again = await gh(
|
|
1171
|
+
["pr", "view", opts.head, "--json", "url", "--jq", ".url"],
|
|
1172
|
+
worktreePath,
|
|
1173
|
+
{ reject: false }
|
|
1174
|
+
);
|
|
1175
|
+
return again.stdout.trim() || "";
|
|
1176
|
+
}
|
|
1177
|
+
const args = [
|
|
1178
|
+
"pr",
|
|
1179
|
+
"create",
|
|
1180
|
+
"--title",
|
|
1181
|
+
opts.title,
|
|
1182
|
+
"--body",
|
|
1183
|
+
opts.body ?? opts.title,
|
|
1184
|
+
"--base",
|
|
1185
|
+
opts.base,
|
|
1186
|
+
"--head",
|
|
1187
|
+
opts.head
|
|
1188
|
+
];
|
|
1189
|
+
if (opts.draft) args.push("--draft");
|
|
1190
|
+
const { stdout } = await gh(args, worktreePath);
|
|
1191
|
+
const url = stdout.trim().split("\n").find((l) => l.startsWith("http")) ?? stdout.trim();
|
|
1192
|
+
return url;
|
|
1193
|
+
}
|
|
1194
|
+
function suggestSlug(source) {
|
|
1195
|
+
const base = slugify(source || "thread");
|
|
1196
|
+
const stamp = Date.now().toString(36).slice(-4);
|
|
1197
|
+
return `${base || "thread"}-${stamp}`;
|
|
1198
|
+
}
|
|
1199
|
+
function sameRepoPath(a, b) {
|
|
1200
|
+
return normalizeWorktreePath(a) === normalizeWorktreePath(b);
|
|
1201
|
+
}
|
|
1202
|
+
function listLocalThreadBranchSlugs(repoPath) {
|
|
1203
|
+
const refsDir = join(repoPath, ".git", "refs", "heads", "thread");
|
|
1204
|
+
if (!existsSync(refsDir)) return [];
|
|
1205
|
+
try {
|
|
1206
|
+
return readdirSync(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
|
|
1207
|
+
} catch {
|
|
1208
|
+
return [];
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
function collectTakenTeamSlugs(repoPath) {
|
|
1212
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1213
|
+
const root = worktreesRoot(repoPath);
|
|
1214
|
+
if (existsSync(root)) {
|
|
1215
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
1216
|
+
if (entry.isDirectory() && entry.name !== ".DS_Store") {
|
|
1217
|
+
taken.add(normalizeTakenSlug(entry.name));
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
for (const slug of listLocalThreadBranchSlugs(repoPath)) {
|
|
1222
|
+
taken.add(slug);
|
|
1223
|
+
}
|
|
1224
|
+
for (const thread of listThreads({ includeArchived: true })) {
|
|
1225
|
+
if (!sameRepoPath(thread.repoPath, repoPath)) continue;
|
|
1226
|
+
for (const slug of takenSlugsFromThread(thread)) {
|
|
1227
|
+
taken.add(slug);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return taken;
|
|
1231
|
+
}
|
|
1232
|
+
function allocateTeamSlug(repoPath) {
|
|
1233
|
+
const taken = collectTakenTeamSlugs(repoPath);
|
|
1234
|
+
for (let attempt = 0; attempt < 32; attempt++) {
|
|
1235
|
+
const team = allocateTeamName(taken);
|
|
1236
|
+
const path = join(worktreesRoot(repoPath), team.slug);
|
|
1237
|
+
if (!existsSync(path)) return team;
|
|
1238
|
+
taken.add(team.slug);
|
|
1239
|
+
}
|
|
1240
|
+
throw new Error("No available soccer team worktree directories left");
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
export {
|
|
1244
|
+
FAMOUS_SOCCER_TEAMS,
|
|
1245
|
+
teamSlugFromName,
|
|
1246
|
+
takenSlugsFromThread,
|
|
1247
|
+
allocateTeamName,
|
|
1248
|
+
normalizeWorktreePath,
|
|
1249
|
+
worktreeNameFromPath,
|
|
1250
|
+
isPlaceholderBranch,
|
|
1251
|
+
branchDisplayLabel,
|
|
1252
|
+
threadDisplayLabel,
|
|
1253
|
+
worktreeDisplayLabel,
|
|
1254
|
+
worktreeDisplayLabelForGroup,
|
|
1255
|
+
slugify,
|
|
1256
|
+
resolveRepoRoot,
|
|
1257
|
+
parseGithubSlugFromRemoteUrl,
|
|
1258
|
+
resolveGithubRepoSlug,
|
|
1259
|
+
resolveDefaultBranch,
|
|
1260
|
+
resolveDiffBaseRef,
|
|
1261
|
+
listBranches,
|
|
1262
|
+
listPrs,
|
|
1263
|
+
getPr,
|
|
1264
|
+
resolvePrSelector,
|
|
1265
|
+
detectLocalMergeConflicts,
|
|
1266
|
+
getPrChecks,
|
|
1267
|
+
getPrDetails,
|
|
1268
|
+
fetchPrHead,
|
|
1269
|
+
resolveWorktreeStartPoint,
|
|
1270
|
+
createThreadWorktree,
|
|
1271
|
+
removeWorktree,
|
|
1272
|
+
listWorktrees,
|
|
1273
|
+
isDirty,
|
|
1274
|
+
currentBranch,
|
|
1275
|
+
commitAll,
|
|
1276
|
+
pushBranch,
|
|
1277
|
+
mergePr,
|
|
1278
|
+
createOrUpdatePr,
|
|
1279
|
+
suggestSlug,
|
|
1280
|
+
collectTakenTeamSlugs,
|
|
1281
|
+
allocateTeamSlug
|
|
1282
|
+
};
|