@plainconceptsplatform/workflows 0.4.33 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -88
- package/loops/actions/agent-output.cjs +17 -17
- package/loops/actions/audit-close/action.yml +24 -0
- package/loops/actions/classify-route/action.yml +4 -3
- package/loops/actions/classify-route/classify-route.sh +69 -29
- package/loops/actions/load-issue-context/action.yml +2 -0
- package/loops/actions/update-changelog/action.yml +113 -0
- package/loops/actions/validate-merge-gate-output/action.yml +40 -0
- package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +34 -0
- package/loops/actions/validate-refine-output/validate-refine-output.sh +16 -3
- package/loops/actions/validate-review-output/action.yml +35 -0
- package/loops/actions/validate-review-output/validate-review-output.sh +30 -0
- package/loops/actions/validate-triage-output/action.yml +36 -0
- package/loops/actions/validate-triage-output/validate-triage-output.sh +36 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +121 -36
- package/loops/scripts/compile-agent-workflows.mjs +195 -6
- package/loops/templates/agentics/agentics-checks.yml +86 -86
- package/loops/templates/agentics/agentics-maintenance.yml +121 -121
- package/loops/templates/ci/app-ci-dotnet-next.yml +268 -171
- package/loops/templates/ci/app-ci-node-monorepo.yml +208 -178
- package/loops/templates/issues/bug_report.yml +109 -109
- package/loops/templates/issues/feature_request.yml +75 -75
- package/loops/templates/opencode/opencode.ci.json +49 -47
- package/loops/templates/opencode/opencode.ci.json.md +46 -41
- package/loops/templates/release/github-release.yml +30 -30
- package/loops/workflows/agent-apply-review.md +465 -373
- package/loops/workflows/agent-audit.md +213 -197
- package/loops/workflows/agent-implement.md +538 -374
- package/loops/workflows/agent-merge-gate.md +730 -485
- package/loops/workflows/agent-refine.md +609 -379
- package/loops/workflows/agent-release.md +258 -0
- package/loops/workflows/agent-triage.md +447 -0
- package/loops/workflows/authorize-bot-work.yml +85 -81
- package/loops/workflows/shared/opencode-ci.md +206 -197
- package/loops/workflows/shared/platform-defaults.md +19 -16
- package/loops/workflows/work-router.yml +862 -551
- package/package.json +42 -42
- package/dist/catalog-installation.d.ts +0 -30
- package/dist/catalog-installation.js +0 -352
- package/dist/catalog-installation.test.d.ts +0 -1
- package/dist/catalog-installation.test.js +0 -448
- package/dist/catalog-listing.d.ts +0 -13
- package/dist/catalog-listing.js +0 -70
- package/dist/catalog-listing.test.d.ts +0 -1
- package/dist/catalog-listing.test.js +0 -150
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -218
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -273
- package/dist/repository-inspection.d.ts +0 -23
- package/dist/repository-inspection.js +0 -113
- package/dist/repository-inspection.test.d.ts +0 -1
- package/dist/repository-inspection.test.js +0 -77
- package/dist/repository-state.d.ts +0 -18
- package/dist/repository-state.js +0 -77
- package/dist/repository-state.test.d.ts +0 -1
- package/dist/repository-state.test.js +0 -96
- package/dist/route-processing.d.ts +0 -6
- package/dist/route-processing.js +0 -150
- package/dist/route-processing.test.d.ts +0 -1
- package/dist/route-processing.test.js +0 -342
- package/dist/stack-defaults.d.ts +0 -11
- package/dist/stack-defaults.js +0 -104
- package/dist/stack-defaults.test.d.ts +0 -1
- package/dist/stack-defaults.test.js +0 -266
- package/dist/tui.d.ts +0 -25
- package/dist/tui.js +0 -287
- package/dist/tui.test.d.ts +0 -1
- package/dist/tui.test.js +0 -246
- package/dist/workflow-catalog.d.ts +0 -25
- package/dist/workflow-catalog.js +0 -57
- package/dist/workflow-catalog.test.d.ts +0 -1
- package/dist/workflow-catalog.test.js +0 -29
- package/loops/workflows/agent-direct.md +0 -363
- package/loops/workflows/agent-propose.md +0 -342
|
@@ -1,551 +1,862 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/work-router.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
-
name: "All Work Router"
|
|
3
|
-
|
|
4
|
-
# Owns every repository automation trigger. Classifies each event into exactly one route,
|
|
5
|
-
# then calls at most one worker or runs one deterministic job. No model runs here.
|
|
6
|
-
|
|
7
|
-
# GitHub titles an issue-triggered run with the issue title, so every run on one issue reads
|
|
8
|
-
# the same. Name it after the event instead. This is evaluated before any job, so it can
|
|
9
|
-
# describe the trigger but not the route the classifier picks.
|
|
10
|
-
run-name: >-
|
|
11
|
-
${{ github.event_name == 'issues'
|
|
12
|
-
&& github.event.
|
|
13
|
-
&&
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
&& github.event.
|
|
17
|
-
&&
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
&& github.event.
|
|
21
|
-
&&
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
&& github.event.
|
|
25
|
-
&& format('Working: {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
26
|
-
|| github.event_name == 'issues'
|
|
27
|
-
&&
|
|
28
|
-
|
|
29
|
-
&& format('
|
|
30
|
-
|| github.event_name == '
|
|
31
|
-
&&
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
&&
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
&&
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
issues
|
|
46
|
-
|
|
47
|
-
issue_comment
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
workflow_run
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
steps:
|
|
199
|
-
- name:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
permissions: write-all
|
|
303
|
-
with:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
secrets:
|
|
307
|
-
OPENAI_API_KEY: ${{ secrets.
|
|
308
|
-
CODEX_API_KEY: ${{ secrets.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
gh
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/work-router.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
+
name: "All Work Router"
|
|
3
|
+
|
|
4
|
+
# Owns every repository automation trigger. Classifies each event into exactly one route,
|
|
5
|
+
# then calls at most one worker or runs one deterministic job. No model runs here.
|
|
6
|
+
|
|
7
|
+
# GitHub titles an issue-triggered run with the issue title, so every run on one issue reads
|
|
8
|
+
# the same. Name it after the event instead. This is evaluated before any job, so it can
|
|
9
|
+
# describe the trigger but not the route the classifier picks.
|
|
10
|
+
run-name: >-
|
|
11
|
+
${{ github.event_name == 'issues'
|
|
12
|
+
&& github.event.action == 'opened'
|
|
13
|
+
&& format('Issue opened: {0} (#{1}) by {2}', github.event.issue.title, github.event.issue.number, github.actor)
|
|
14
|
+
|| github.event_name == 'issues'
|
|
15
|
+
&& github.event.label.name == 'bot-working'
|
|
16
|
+
&& contains(github.event.issue.labels.*.name, 'implement')
|
|
17
|
+
&& format('Working (Implement): {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
18
|
+
|| github.event_name == 'issues'
|
|
19
|
+
&& github.event.label.name == 'bot-working'
|
|
20
|
+
&& contains(github.event.issue.labels.*.name, 'refine')
|
|
21
|
+
&& format('Working (Refine): {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
22
|
+
|| github.event_name == 'issues'
|
|
23
|
+
&& github.event.label.name == 'bot-working'
|
|
24
|
+
&& contains(github.event.issue.labels.*.name, 'direct')
|
|
25
|
+
&& format('Working (Direct): {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
26
|
+
|| github.event_name == 'issues'
|
|
27
|
+
&& github.event.label.name == 'bot-working'
|
|
28
|
+
&& contains(github.event.issue.labels.*.name, 'triage')
|
|
29
|
+
&& format('Working (Triage): {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
30
|
+
|| github.event_name == 'issues'
|
|
31
|
+
&& github.event.label.name == 'bot-working'
|
|
32
|
+
&& format('Working: {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
33
|
+
|| github.event_name == 'issues'
|
|
34
|
+
&& github.event.label.name == 'implement'
|
|
35
|
+
&& endsWith(github.actor, '[bot]')
|
|
36
|
+
&& format('Working (Implement): {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
37
|
+
|| github.event_name == 'issues'
|
|
38
|
+
&& github.event.label.name == 'refine'
|
|
39
|
+
&& endsWith(github.actor, '[bot]')
|
|
40
|
+
&& format('Working (Refine): {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
41
|
+
|| github.event_name == 'issues'
|
|
42
|
+
&& github.event.label.name == 'direct'
|
|
43
|
+
&& endsWith(github.actor, '[bot]')
|
|
44
|
+
&& format('Working (Direct): {0} (#{1})', github.event.issue.title, github.event.issue.number)
|
|
45
|
+
|| github.event_name == 'issues'
|
|
46
|
+
&& format('{0} label on #{1} by {2}', github.event.label.name, github.event.issue.number, github.actor)
|
|
47
|
+
|| github.event_name == 'issue_comment'
|
|
48
|
+
&& format('comment on #{0} by {1}', github.event.issue.number, github.actor)
|
|
49
|
+
|| github.event_name == 'pull_request_review'
|
|
50
|
+
&& format('review on PR #{0} by {1}', github.event.pull_request.number, github.actor)
|
|
51
|
+
|| github.event_name == 'pull_request_review_comment'
|
|
52
|
+
&& format('review comment on PR #{0} by {1}', github.event.pull_request.number, github.actor)
|
|
53
|
+
|| github.event_name == 'pull_request_target'
|
|
54
|
+
&& format('PR #{0} {1} by {2}', github.event.pull_request.number, github.event.action, github.actor)
|
|
55
|
+
|| github.event_name == 'workflow_run'
|
|
56
|
+
&& format('CI {0} on {1}', github.event.workflow_run.conclusion, github.event.workflow_run.head_branch)
|
|
57
|
+
|| github.event_name == 'workflow_dispatch'
|
|
58
|
+
&& inputs.operation == 'triage'
|
|
59
|
+
&& format('Working (Triage): #{0}', inputs.issue-number)
|
|
60
|
+
|| github.event_name == 'workflow_dispatch'
|
|
61
|
+
&& inputs.operation == 'refine'
|
|
62
|
+
&& format('Working (Refine): #{0}', inputs.issue-number)
|
|
63
|
+
|| github.event_name == 'workflow_dispatch'
|
|
64
|
+
&& inputs.operation == 'implement'
|
|
65
|
+
&& format('Working (Implement): #{0}', inputs.issue-number)
|
|
66
|
+
|| github.event_name == 'workflow_dispatch'
|
|
67
|
+
&& inputs.operation == 'merge-gate'
|
|
68
|
+
&& format('Working (Merge Gate): PR #{0}', inputs.pr-number)
|
|
69
|
+
|| github.event_name == 'workflow_dispatch'
|
|
70
|
+
&& inputs.operation == 'batch'
|
|
71
|
+
&& format('Working (Batch): #{0}', inputs.issue-number)
|
|
72
|
+
|| github.event_name == 'workflow_dispatch'
|
|
73
|
+
&& format('dispatch: {0}', inputs.operation)
|
|
74
|
+
|| github.event_name == 'schedule'
|
|
75
|
+
&& format('schedule {0}', github.event.schedule)
|
|
76
|
+
|| github.event_name }}
|
|
77
|
+
|
|
78
|
+
on:
|
|
79
|
+
issues:
|
|
80
|
+
types: [opened, labeled]
|
|
81
|
+
issue_comment:
|
|
82
|
+
types: [created]
|
|
83
|
+
pull_request_review_comment:
|
|
84
|
+
types: [created]
|
|
85
|
+
pull_request_review:
|
|
86
|
+
types: [submitted]
|
|
87
|
+
|
|
88
|
+
pull_request_target:
|
|
89
|
+
# opened / reopened / synchronize feed conflict detection (detect-pr-conflicts) and
|
|
90
|
+
# bot-approve. closed feeds detect-pr-conflicts: when a PR merges, the target branch
|
|
91
|
+
# moved and previously-mergeable sibling bot PRs may now conflict.
|
|
92
|
+
types: [opened, synchronize, reopened, labeled, closed]
|
|
93
|
+
|
|
94
|
+
# CI completion on a pull request. Enabled so a FAILED CI run dispatches the Merge Gate
|
|
95
|
+
# immediately instead of waiting for the hourly reconcile. The classifier admits only
|
|
96
|
+
# conclusion == failure with an attached pull request; a green run still reaches merge
|
|
97
|
+
# through app-ci's dispatch-merge-gate and the reconcile belt, so routing success here
|
|
98
|
+
# would double-fire the gate on every passing PR.
|
|
99
|
+
#
|
|
100
|
+
# Caveat that still holds (2026-08-28): GitHub does not deliver workflow_run for CI runs
|
|
101
|
+
# whose originating event came from GITHUB_TOKEN, so a bot-opened PR's own CI completion
|
|
102
|
+
# may never arrive here — reconcile-bot-pr-runs remains the belt for that gap. Security:
|
|
103
|
+
# workflow_run runs from the default branch with secrets, but the gate's subject job
|
|
104
|
+
# rejects any PR that is not open and bot-authored, so a fork PR's failing CI cannot
|
|
105
|
+
# start privileged work.
|
|
106
|
+
workflow_run:
|
|
107
|
+
workflows: ["App: CI"]
|
|
108
|
+
types: [completed]
|
|
109
|
+
|
|
110
|
+
# Cron to route mapping lives in .github/actions/classify-route/classify-route.sh.
|
|
111
|
+
schedule:
|
|
112
|
+
# Slot 0. Every consumer installs this same file, and the agent fleet is one shared,
|
|
113
|
+
# two-VM scale set, so a second repository left on slot 0 audits at the same minute as
|
|
114
|
+
# the first. Pick a free slot per repository: see "Scheduling across repositories" in the
|
|
115
|
+
# package README, and change AUDIT_CRON in classify-route.sh to match, or the run fires
|
|
116
|
+
# and classifies to no route.
|
|
117
|
+
- cron: "17 1 * * 1"
|
|
118
|
+
- cron: "43 3 * * *"
|
|
119
|
+
- cron: "0 6 * * *"
|
|
120
|
+
- cron: "17 * * * *"
|
|
121
|
+
- cron: "29 7 * * *"
|
|
122
|
+
|
|
123
|
+
workflow_dispatch:
|
|
124
|
+
inputs:
|
|
125
|
+
operation:
|
|
126
|
+
description: "Operation to run"
|
|
127
|
+
required: true
|
|
128
|
+
type: choice
|
|
129
|
+
options:
|
|
130
|
+
- refine
|
|
131
|
+
- implement
|
|
132
|
+
- triage
|
|
133
|
+
- apply-review
|
|
134
|
+
- merge-gate
|
|
135
|
+
- audit
|
|
136
|
+
- audit-close
|
|
137
|
+
- cleanup-artifacts
|
|
138
|
+
- reconcile-bot-pr-runs
|
|
139
|
+
- validate
|
|
140
|
+
- release
|
|
141
|
+
issue-number:
|
|
142
|
+
description: "Issue number (required for refine / implement / direct)"
|
|
143
|
+
required: false
|
|
144
|
+
type: string
|
|
145
|
+
pr-number:
|
|
146
|
+
description: "Pull request number (required for apply-review / merge-gate)"
|
|
147
|
+
required: false
|
|
148
|
+
type: string
|
|
149
|
+
mode:
|
|
150
|
+
description: "Refine mode: first or rerefine"
|
|
151
|
+
required: false
|
|
152
|
+
type: choice
|
|
153
|
+
options:
|
|
154
|
+
- first
|
|
155
|
+
- rerefine
|
|
156
|
+
ci-conclusion:
|
|
157
|
+
description: "CI conclusion for merge-gate dispatch"
|
|
158
|
+
required: false
|
|
159
|
+
type: choice
|
|
160
|
+
options:
|
|
161
|
+
- success
|
|
162
|
+
- failure
|
|
163
|
+
- action_required
|
|
164
|
+
ci-run-id:
|
|
165
|
+
description: "CI workflow run ID for merge-gate dispatch"
|
|
166
|
+
required: false
|
|
167
|
+
type: string
|
|
168
|
+
trigger-kind:
|
|
169
|
+
description: "Audit trigger: scheduled or manual"
|
|
170
|
+
required: false
|
|
171
|
+
type: choice
|
|
172
|
+
options:
|
|
173
|
+
- manual
|
|
174
|
+
- scheduled
|
|
175
|
+
version-bump:
|
|
176
|
+
description: "Release version bump: patch, minor, major, or auto"
|
|
177
|
+
required: false
|
|
178
|
+
type: choice
|
|
179
|
+
options:
|
|
180
|
+
- auto
|
|
181
|
+
- patch
|
|
182
|
+
- minor
|
|
183
|
+
- major
|
|
184
|
+
|
|
185
|
+
permissions:
|
|
186
|
+
contents: read
|
|
187
|
+
|
|
188
|
+
jobs:
|
|
189
|
+
# Public repositories must check repository permission before an event can start a model.
|
|
190
|
+
authorize:
|
|
191
|
+
runs-on: ubuntu-latest
|
|
192
|
+
timeout-minutes: 5
|
|
193
|
+
permissions:
|
|
194
|
+
contents: read
|
|
195
|
+
outputs:
|
|
196
|
+
trusted: ${{ steps.check.outputs.trusted }}
|
|
197
|
+
is_outside_collaborator: ${{ steps.check.outputs.is_outside_collaborator }}
|
|
198
|
+
steps:
|
|
199
|
+
- name: Check the actor can start an agent
|
|
200
|
+
id: check
|
|
201
|
+
env:
|
|
202
|
+
GH_TOKEN: ${{ github.token }}
|
|
203
|
+
ACTOR: ${{ github.actor }}
|
|
204
|
+
EVENT: ${{ github.event_name }}
|
|
205
|
+
run: |
|
|
206
|
+
set -euo pipefail
|
|
207
|
+
|
|
208
|
+
# Events with no human actor. `schedule` and `workflow_run` are the fleet triggering
|
|
209
|
+
# itself; `workflow_dispatch` is already restricted by GitHub to write and above.
|
|
210
|
+
case "$EVENT" in
|
|
211
|
+
schedule | workflow_run | workflow_dispatch)
|
|
212
|
+
echo "trusted=true" >> "$GITHUB_OUTPUT"
|
|
213
|
+
echo "is_outside_collaborator=false" >> "$GITHUB_OUTPUT"
|
|
214
|
+
echo "::notice::$EVENT has no untrusted actor."
|
|
215
|
+
exit 0
|
|
216
|
+
;;
|
|
217
|
+
esac
|
|
218
|
+
|
|
219
|
+
# Platform bot owns Safe Outputs. Its label changes must be able to
|
|
220
|
+
# start the follow-on worker, even though GitHub does not report it
|
|
221
|
+
# as a repository collaborator.
|
|
222
|
+
if [ "$ACTOR" = "github-actions[bot]" ] || [ "$ACTOR" = "platform-devbox[bot]" ]; then
|
|
223
|
+
echo "trusted=true" >> "$GITHUB_OUTPUT"
|
|
224
|
+
echo "is_outside_collaborator=false" >> "$GITHUB_OUTPUT"
|
|
225
|
+
exit 0
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$ACTOR/permission" \
|
|
229
|
+
--jq '.permission' 2>/dev/null || echo "none")
|
|
230
|
+
|
|
231
|
+
# Check if the actor is an organization member. Outside collaborators can have
|
|
232
|
+
# write/maintain permission on a repo without being org members. Triage is for
|
|
233
|
+
# outside collaborators only — a non-member with write access is still outside.
|
|
234
|
+
org=$(echo "$GITHUB_REPOSITORY" | cut -d/ -f1)
|
|
235
|
+
is_member=false
|
|
236
|
+
if gh api "orgs/$org/members/$ACTOR" >/dev/null 2>&1; then
|
|
237
|
+
is_member=true
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
case "$permission" in
|
|
241
|
+
admin | maintain | write)
|
|
242
|
+
if [ "$is_member" = "true" ]; then
|
|
243
|
+
echo "trusted=true" >> "$GITHUB_OUTPUT"
|
|
244
|
+
echo "is_outside_collaborator=false" >> "$GITHUB_OUTPUT"
|
|
245
|
+
echo "::notice::$ACTOR has $permission permission and is an org member."
|
|
246
|
+
else
|
|
247
|
+
echo "trusted=true" >> "$GITHUB_OUTPUT"
|
|
248
|
+
echo "is_outside_collaborator=true" >> "$GITHUB_OUTPUT"
|
|
249
|
+
echo "::notice::$ACTOR has $permission permission but is NOT an org member (outside collaborator). Triage route eligible."
|
|
250
|
+
fi
|
|
251
|
+
;;
|
|
252
|
+
read)
|
|
253
|
+
echo "trusted=false" >> "$GITHUB_OUTPUT"
|
|
254
|
+
echo "is_outside_collaborator=true" >> "$GITHUB_OUTPUT"
|
|
255
|
+
echo "::notice::$ACTOR is an outside collaborator (read permission). Triage route eligible."
|
|
256
|
+
;;
|
|
257
|
+
*)
|
|
258
|
+
echo "trusted=false" >> "$GITHUB_OUTPUT"
|
|
259
|
+
echo "is_outside_collaborator=false" >> "$GITHUB_OUTPUT"
|
|
260
|
+
echo "::notice::$ACTOR has '$permission' permission on this repository. No agent will run."
|
|
261
|
+
;;
|
|
262
|
+
esac
|
|
263
|
+
|
|
264
|
+
classify:
|
|
265
|
+
runs-on: ubuntu-latest
|
|
266
|
+
timeout-minutes: 5
|
|
267
|
+
permissions:
|
|
268
|
+
contents: read
|
|
269
|
+
pull-requests: read
|
|
270
|
+
outputs:
|
|
271
|
+
route: ${{ steps.route.outputs.route }}
|
|
272
|
+
issue-number: ${{ steps.route.outputs.issue-number }}
|
|
273
|
+
pr-number: ${{ steps.route.outputs.pr-number }}
|
|
274
|
+
linked-issue: ${{ steps.route.outputs.linked-issue }}
|
|
275
|
+
ci-conclusion: ${{ steps.route.outputs.ci-conclusion }}
|
|
276
|
+
ci-run-id: ${{ steps.route.outputs.ci-run-id }}
|
|
277
|
+
refine-mode: ${{ steps.route.outputs.refine-mode }}
|
|
278
|
+
triage-mode: ${{ steps.route.outputs.triage-mode }}
|
|
279
|
+
trigger-kind: ${{ steps.route.outputs.trigger-kind }}
|
|
280
|
+
steps:
|
|
281
|
+
- name: Checkout workflow actions
|
|
282
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
283
|
+
with:
|
|
284
|
+
persist-credentials: false
|
|
285
|
+
- name: Classify event into a single route
|
|
286
|
+
id: route
|
|
287
|
+
uses: ./.github/actions/classify-route
|
|
288
|
+
with:
|
|
289
|
+
token: ${{ github.token }}
|
|
290
|
+
|
|
291
|
+
call-refine:
|
|
292
|
+
needs: [classify, authorize]
|
|
293
|
+
if: needs.classify.outputs.route == 'refine' && needs.authorize.outputs.trusted == 'true'
|
|
294
|
+
uses: ./.github/workflows/agent-refine.lock.yml
|
|
295
|
+
concurrency:
|
|
296
|
+
group: refine-${{ needs.classify.outputs.issue-number }}
|
|
297
|
+
cancel-in-progress: false
|
|
298
|
+
# write-all, not a narrower map: every worker's agent job declares `permissions: read-all`,
|
|
299
|
+
# which requests read on every scope. A caller granting less cannot satisfy it, and GitHub
|
|
300
|
+
# rejects the call with a startup failure before any job is created. The real boundary is the
|
|
301
|
+
# worker's own read-all plus safe-outputs, not this grant.
|
|
302
|
+
permissions: write-all
|
|
303
|
+
with:
|
|
304
|
+
issue-number: ${{ needs.classify.outputs.issue-number }}
|
|
305
|
+
mode: ${{ needs.classify.outputs.refine-mode }}
|
|
306
|
+
secrets:
|
|
307
|
+
OPENAI_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
308
|
+
CODEX_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
309
|
+
COPILOT_GITHUB_TOKEN: ${{ secrets.FORGE_API_KEY }}
|
|
310
|
+
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
311
|
+
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
312
|
+
AGENTMEMORY_SECRET: ${{ secrets.AGENTMEMORY_SECRET }}
|
|
313
|
+
|
|
314
|
+
# Checks whether an open bot PR already exists for the issue. If it does, implement is
|
|
315
|
+
# skipped — the merge-gate will handle fixing the existing PR. This prevents duplicate
|
|
316
|
+
# PRs when retries are triggered after a merge-gate failure.
|
|
317
|
+
check-implement-pr:
|
|
318
|
+
needs: [classify, authorize]
|
|
319
|
+
if: needs.classify.outputs.route == 'implement' && needs.authorize.outputs.trusted == 'true'
|
|
320
|
+
runs-on: ubuntu-latest
|
|
321
|
+
timeout-minutes: 3
|
|
322
|
+
permissions:
|
|
323
|
+
contents: read
|
|
324
|
+
pull-requests: read
|
|
325
|
+
outputs:
|
|
326
|
+
has-open-pr: ${{ steps.check.outputs.has-open-pr }}
|
|
327
|
+
steps:
|
|
328
|
+
- name: Check for existing open bot PR
|
|
329
|
+
id: check
|
|
330
|
+
env:
|
|
331
|
+
GH_TOKEN: ${{ github.token }}
|
|
332
|
+
REPO: ${{ github.repository }}
|
|
333
|
+
ISSUE_NUMBER: ${{ needs.classify.outputs.issue-number }}
|
|
334
|
+
run: |
|
|
335
|
+
set -euo pipefail
|
|
336
|
+
# A refine-to-implement transition emits two label events (the implement label,
|
|
337
|
+
# then the worker re-adding bot-working) and each one classifies to implement.
|
|
338
|
+
# The older router run wins: a younger run that finds an older live implement
|
|
339
|
+
# route for the same issue skips instead of queueing a duplicate worker. Only
|
|
340
|
+
# bot-actor titles are counted, so a human's "waiting for bot-working" run can
|
|
341
|
+
# never block the real one.
|
|
342
|
+
for st in in_progress queued; do
|
|
343
|
+
dupes=$(gh api "repos/$REPO/actions/workflows/work-router.yml/runs?status=$st&per_page=100" --jq "[.workflow_runs[]
|
|
344
|
+
| select(.id < $GITHUB_RUN_ID)
|
|
345
|
+
| select(
|
|
346
|
+
((.display_title | startswith(\"Working (Implement)\")) and (.display_title | contains(\"(#${ISSUE_NUMBER})\")))
|
|
347
|
+
or
|
|
348
|
+
((.display_title | contains(\"implement label on #${ISSUE_NUMBER} by\")) and (.display_title | endswith(\"[bot]\")))
|
|
349
|
+
)
|
|
350
|
+
] | length" 2>/dev/null || echo 0)
|
|
351
|
+
if [ "${dupes:-0}" -gt 0 ]; then
|
|
352
|
+
echo "has-open-pr=true" >> "$GITHUB_OUTPUT"
|
|
353
|
+
echo "::notice::An older router run is already implementing #$ISSUE_NUMBER; skipping the duplicate."
|
|
354
|
+
exit 0
|
|
355
|
+
fi
|
|
356
|
+
done
|
|
357
|
+
|
|
358
|
+
# Marker-first: implement stamps <!-- implement-pr: N --> on the issue when its
|
|
359
|
+
# pull request is created, so the duplicate check is an exact lookup. Only an
|
|
360
|
+
# OPEN marker PR blocks; a merged or closed one means a re-implement is legitimate.
|
|
361
|
+
marker_pr=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json body --jq '.body // ""' \
|
|
362
|
+
| grep -oE '<!-- implement-pr: [0-9]+ -->' | head -1 | grep -oE '[0-9]+' || true)
|
|
363
|
+
if [ -n "$marker_pr" ]; then
|
|
364
|
+
state=$(gh pr view "$marker_pr" --repo "$REPO" --json state --jq '.state' 2>/dev/null || echo GONE)
|
|
365
|
+
if [ "$state" = "OPEN" ]; then
|
|
366
|
+
echo "has-open-pr=true" >> "$GITHUB_OUTPUT"
|
|
367
|
+
echo "::notice::Issue #$ISSUE_NUMBER is marked as implemented by open PR #$marker_pr. Skipping duplicate implement."
|
|
368
|
+
exit 0
|
|
369
|
+
fi
|
|
370
|
+
echo "Marker PR #$marker_pr is $state; a fresh implement is allowed."
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
existing=$(gh pr list --repo "$REPO" --state open --json number,author,body \
|
|
374
|
+
--jq "[.[]
|
|
375
|
+
| select(.author.login | startswith(\"app/\") or endswith(\"[bot]\"))
|
|
376
|
+
| (.body | ascii_downcase) as \$body
|
|
377
|
+
| select(\$body | contains(\"close #${ISSUE_NUMBER}\") or contains(\"closes #${ISSUE_NUMBER}\") or contains(\"closed #${ISSUE_NUMBER}\") or contains(\"fix #${ISSUE_NUMBER}\") or contains(\"fixes #${ISSUE_NUMBER}\") or contains(\"fixed #${ISSUE_NUMBER}\") or contains(\"resolve #${ISSUE_NUMBER}\") or contains(\"resolves #${ISSUE_NUMBER}\") or contains(\"resolved #${ISSUE_NUMBER}\"))
|
|
378
|
+
] | length")
|
|
379
|
+
if [ "$existing" -gt 0 ]; then
|
|
380
|
+
echo "has-open-pr=true" >> "$GITHUB_OUTPUT"
|
|
381
|
+
echo "::notice::Issue #$ISSUE_NUMBER already has an open bot PR. Skipping implement to prevent duplicates."
|
|
382
|
+
else
|
|
383
|
+
echo "has-open-pr=false" >> "$GITHUB_OUTPUT"
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
call-implement:
|
|
387
|
+
needs: [classify, authorize, check-implement-pr]
|
|
388
|
+
if: needs.classify.outputs.route == 'implement' && needs.authorize.outputs.trusted == 'true' && needs.check-implement-pr.outputs.has-open-pr != 'true'
|
|
389
|
+
uses: ./.github/workflows/agent-implement.lock.yml
|
|
390
|
+
concurrency:
|
|
391
|
+
group: implement-global
|
|
392
|
+
cancel-in-progress: false
|
|
393
|
+
# FIFO, up to 100 waiting. Without this the default `queue: single` cancels whatever
|
|
394
|
+
# was pending, so labelling six issues at once implements one and drops the rest.
|
|
395
|
+
queue: max
|
|
396
|
+
permissions: write-all
|
|
397
|
+
with:
|
|
398
|
+
issue-number: ${{ needs.classify.outputs.issue-number }}
|
|
399
|
+
secrets:
|
|
400
|
+
OPENAI_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
401
|
+
CODEX_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
402
|
+
COPILOT_GITHUB_TOKEN: ${{ secrets.FORGE_API_KEY }}
|
|
403
|
+
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
404
|
+
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
405
|
+
AGENTMEMORY_SECRET: ${{ secrets.AGENTMEMORY_SECRET }}
|
|
406
|
+
|
|
407
|
+
dispatch-triage:
|
|
408
|
+
needs: [classify, authorize]
|
|
409
|
+
if: needs.classify.outputs.route == 'triage' && needs.authorize.outputs.is_outside_collaborator == 'true'
|
|
410
|
+
runs-on: ubuntu-latest
|
|
411
|
+
timeout-minutes: 5
|
|
412
|
+
permissions:
|
|
413
|
+
actions: write
|
|
414
|
+
contents: read
|
|
415
|
+
steps:
|
|
416
|
+
- name: Create bot token
|
|
417
|
+
id: app-token
|
|
418
|
+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
419
|
+
with:
|
|
420
|
+
client-id: ${{ secrets.BOT_APP_ID }}
|
|
421
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
422
|
+
- name: Dispatch trusted triage run
|
|
423
|
+
env:
|
|
424
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
425
|
+
REPO: ${{ github.repository }}
|
|
426
|
+
REF: ${{ github.event.repository.default_branch }}
|
|
427
|
+
ISSUE_NUMBER: ${{ needs.classify.outputs.issue-number }}
|
|
428
|
+
MODE: ${{ needs.classify.outputs.triage-mode }}
|
|
429
|
+
run: |
|
|
430
|
+
set -euo pipefail
|
|
431
|
+
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
|
|
432
|
+
-f operation=triage \
|
|
433
|
+
-f issue-number="$ISSUE_NUMBER" \
|
|
434
|
+
-f mode="$MODE"
|
|
435
|
+
|
|
436
|
+
call-triage:
|
|
437
|
+
needs: [classify, authorize]
|
|
438
|
+
# Triage on issue open is for outside collaborators only (handled by dispatch-triage).
|
|
439
|
+
# A trusted actor who opens an issue should not start triage — they can add refine/implement/direct
|
|
440
|
+
# labels or manually dispatch triage. All other triage routes (labeled, comment, dispatch) are trusted.
|
|
441
|
+
if: needs.classify.outputs.route == 'triage' && needs.authorize.outputs.trusted == 'true' && !(github.event_name == 'issues' && github.event.action == 'opened')
|
|
442
|
+
uses: ./.github/workflows/agent-triage.lock.yml
|
|
443
|
+
concurrency:
|
|
444
|
+
group: triage-${{ needs.classify.outputs.issue-number }}
|
|
445
|
+
cancel-in-progress: false
|
|
446
|
+
permissions: write-all
|
|
447
|
+
with:
|
|
448
|
+
issue-number: ${{ needs.classify.outputs.issue-number }}
|
|
449
|
+
mode: ${{ needs.classify.outputs.triage-mode }}
|
|
450
|
+
secrets:
|
|
451
|
+
OPENAI_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
452
|
+
CODEX_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
453
|
+
COPILOT_GITHUB_TOKEN: ${{ secrets.FORGE_API_KEY }}
|
|
454
|
+
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
455
|
+
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
456
|
+
AGENTMEMORY_SECRET: ${{ secrets.AGENTMEMORY_SECRET }}
|
|
457
|
+
|
|
458
|
+
call-apply-review:
|
|
459
|
+
needs: [classify, authorize]
|
|
460
|
+
if: needs.classify.outputs.route == 'apply-review' && needs.authorize.outputs.trusted == 'true'
|
|
461
|
+
uses: ./.github/workflows/agent-apply-review.lock.yml
|
|
462
|
+
concurrency:
|
|
463
|
+
group: pr-feedback-${{ needs.classify.outputs.pr-number }}
|
|
464
|
+
cancel-in-progress: false
|
|
465
|
+
permissions: write-all
|
|
466
|
+
with:
|
|
467
|
+
pr-number: ${{ needs.classify.outputs.pr-number }}
|
|
468
|
+
linked-issue: ${{ needs.classify.outputs.linked-issue }}
|
|
469
|
+
secrets:
|
|
470
|
+
OPENAI_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
471
|
+
CODEX_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
472
|
+
COPILOT_GITHUB_TOKEN: ${{ secrets.FORGE_API_KEY }}
|
|
473
|
+
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
474
|
+
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
475
|
+
AGENTMEMORY_SECRET: ${{ secrets.AGENTMEMORY_SECRET }}
|
|
476
|
+
|
|
477
|
+
# Shares the write-pipeline group with implement so the two never race on one issue.
|
|
478
|
+
# Falls back to the pull request number only when the closing issue cannot be resolved.
|
|
479
|
+
call-merge-gate:
|
|
480
|
+
needs: classify
|
|
481
|
+
if: needs.classify.outputs.route == 'merge-gate'
|
|
482
|
+
uses: ./.github/workflows/agent-merge-gate.lock.yml
|
|
483
|
+
concurrency:
|
|
484
|
+
group: write-pipeline-${{ needs.classify.outputs.linked-issue || format('pr-{0}', needs.classify.outputs.pr-number) }}
|
|
485
|
+
cancel-in-progress: false
|
|
486
|
+
permissions: write-all
|
|
487
|
+
with:
|
|
488
|
+
pr-number: ${{ needs.classify.outputs.pr-number }}
|
|
489
|
+
linked-issue: ${{ needs.classify.outputs.linked-issue }}
|
|
490
|
+
ci-conclusion: ${{ needs.classify.outputs.ci-conclusion }}
|
|
491
|
+
ci-run-id: ${{ needs.classify.outputs.ci-run-id }}
|
|
492
|
+
secrets:
|
|
493
|
+
OPENAI_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
494
|
+
CODEX_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
495
|
+
COPILOT_GITHUB_TOKEN: ${{ secrets.FORGE_API_KEY }}
|
|
496
|
+
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
497
|
+
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
498
|
+
AGENTMEMORY_SECRET: ${{ secrets.AGENTMEMORY_SECRET }}
|
|
499
|
+
|
|
500
|
+
call-audit:
|
|
501
|
+
needs: classify
|
|
502
|
+
if: needs.classify.outputs.route == 'audit'
|
|
503
|
+
uses: ./.github/workflows/agent-audit.lock.yml
|
|
504
|
+
concurrency:
|
|
505
|
+
group: audit
|
|
506
|
+
cancel-in-progress: false
|
|
507
|
+
permissions: write-all
|
|
508
|
+
with:
|
|
509
|
+
trigger-kind: ${{ needs.classify.outputs.trigger-kind }}
|
|
510
|
+
secrets:
|
|
511
|
+
OPENAI_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
512
|
+
CODEX_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
513
|
+
COPILOT_GITHUB_TOKEN: ${{ secrets.FORGE_API_KEY }}
|
|
514
|
+
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
515
|
+
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
516
|
+
AGENTMEMORY_SECRET: ${{ secrets.AGENTMEMORY_SECRET }}
|
|
517
|
+
|
|
518
|
+
call-release:
|
|
519
|
+
needs: [classify, authorize]
|
|
520
|
+
if: needs.classify.outputs.route == 'release' && needs.authorize.outputs.trusted == 'true'
|
|
521
|
+
uses: ./.github/workflows/agent-release.lock.yml
|
|
522
|
+
permissions: write-all
|
|
523
|
+
with:
|
|
524
|
+
version-bump: ${{ github.event.inputs.version-bump || 'auto' }}
|
|
525
|
+
secrets:
|
|
526
|
+
OPENAI_API_KEY: ${{ secrets.FORGE_API_KEY }}
|
|
527
|
+
|
|
528
|
+
bot-approve:
|
|
529
|
+
needs: classify
|
|
530
|
+
if: >
|
|
531
|
+
needs.classify.outputs.route == 'bot-approve' &&
|
|
532
|
+
(github.actor == 'app/github-actions' || github.actor == 'platform-devbox[bot]')
|
|
533
|
+
runs-on: ubuntu-latest
|
|
534
|
+
timeout-minutes: 5
|
|
535
|
+
permissions:
|
|
536
|
+
actions: write
|
|
537
|
+
steps:
|
|
538
|
+
- name: Approve pending workflow runs
|
|
539
|
+
env:
|
|
540
|
+
GH_TOKEN: ${{ github.token }}
|
|
541
|
+
REPO: ${{ github.repository }}
|
|
542
|
+
BRANCH: ${{ github.head_ref }}
|
|
543
|
+
run: |
|
|
544
|
+
set -euo pipefail
|
|
545
|
+
|
|
546
|
+
mapfile -t run_ids < <(
|
|
547
|
+
gh api "repos/$REPO/actions/runs?status=action_required&branch=$BRANCH" \
|
|
548
|
+
--jq '.workflow_runs[].id'
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
if [ "${#run_ids[@]}" -eq 0 ]; then
|
|
552
|
+
echo "No runs awaiting approval on branch $BRANCH"
|
|
553
|
+
exit 0
|
|
554
|
+
fi
|
|
555
|
+
|
|
556
|
+
for run_id in "${run_ids[@]}"; do
|
|
557
|
+
echo "Approving run $run_id"
|
|
558
|
+
gh api "repos/$REPO/actions/runs/$run_id/approve" --method POST
|
|
559
|
+
done
|
|
560
|
+
|
|
561
|
+
# Conflict watcher for bot pull requests. Merge-conflict state is only available from the
|
|
562
|
+
# mergeability API, which the pure classifier cannot read, so this is an event-gated
|
|
563
|
+
# deterministic dispatcher rather than a classify route (it runs alongside bot-approve on
|
|
564
|
+
# the same pull_request_target event). It NEVER checks out or runs pull request code — it
|
|
565
|
+
# reads mergeability through the API and dispatches the Merge Gate — so it is safe on
|
|
566
|
+
# pull_request_target. Two entry points:
|
|
567
|
+
# opened / reopened / synchronize → check the pull request that changed. Gated to this
|
|
568
|
+
# repository's own branches so a fork PR never starts this App-token job.
|
|
569
|
+
# closed & merged → the target branch moved, so re-check every open bot
|
|
570
|
+
# pull request that may have just become conflicted (the "main changed after another PR
|
|
571
|
+
# merged" case). A merge into the default branch is an inherently trusted internal event.
|
|
572
|
+
# It dispatches only for bot-authored, open, non-draft pull requests whose head is this
|
|
573
|
+
# repository AND whose CI has already concluded (dispatching while CI is still pending would
|
|
574
|
+
# make the gate read a 'pending' verdict and prematurely ask for human review; that PR is
|
|
575
|
+
# covered by the CI-completion route instead). The gate's subject job re-validates ownership
|
|
576
|
+
# before any agent runs.
|
|
577
|
+
detect-pr-conflicts:
|
|
578
|
+
needs: classify
|
|
579
|
+
if: >
|
|
580
|
+
github.event_name == 'pull_request_target' &&
|
|
581
|
+
(
|
|
582
|
+
(github.event.action == 'closed' && github.event.pull_request.merged == true) ||
|
|
583
|
+
(
|
|
584
|
+
(github.event.action == 'opened' ||
|
|
585
|
+
github.event.action == 'reopened' ||
|
|
586
|
+
github.event.action == 'synchronize') &&
|
|
587
|
+
github.event.pull_request.head.repo.full_name == github.repository
|
|
588
|
+
)
|
|
589
|
+
)
|
|
590
|
+
runs-on: ubuntu-latest
|
|
591
|
+
timeout-minutes: 10
|
|
592
|
+
permissions:
|
|
593
|
+
contents: read
|
|
594
|
+
pull-requests: read
|
|
595
|
+
actions: write
|
|
596
|
+
steps:
|
|
597
|
+
- name: Checkout for App token action
|
|
598
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
599
|
+
with:
|
|
600
|
+
persist-credentials: false
|
|
601
|
+
- name: Generate App token
|
|
602
|
+
id: app-token
|
|
603
|
+
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
|
604
|
+
with:
|
|
605
|
+
app-id: ${{ secrets.BOT_APP_ID }}
|
|
606
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
607
|
+
- name: Dispatch Merge Gate for conflicting bot pull requests
|
|
608
|
+
env:
|
|
609
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
610
|
+
REPO: ${{ github.repository }}
|
|
611
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
612
|
+
ACTION: ${{ github.event.action }}
|
|
613
|
+
EVENT_PR: ${{ github.event.pull_request.number }}
|
|
614
|
+
run: |
|
|
615
|
+
set -euo pipefail
|
|
616
|
+
|
|
617
|
+
# Dispatch the Merge Gate for one pull request when it is a conflicting, open,
|
|
618
|
+
# non-draft, bot-authored pull request on this repository whose CI has concluded.
|
|
619
|
+
maybe_dispatch() {
|
|
620
|
+
pr="$1"
|
|
621
|
+
[ -n "$pr" ] || return 0
|
|
622
|
+
|
|
623
|
+
meta=$(gh api "repos/$REPO/pulls/$pr" \
|
|
624
|
+
--jq '{state:.state, draft:.draft, bot:.user.type, head:(.head.repo.full_name // "")}' \
|
|
625
|
+
2>/dev/null || echo '')
|
|
626
|
+
[ -n "$meta" ] || return 0
|
|
627
|
+
[ "$(jq -r '.state' <<<"$meta")" = "open" ] || return 0
|
|
628
|
+
[ "$(jq -r '.draft' <<<"$meta")" = "false" ] || return 0
|
|
629
|
+
[ "$(jq -r '.bot' <<<"$meta")" = "Bot" ] || return 0
|
|
630
|
+
[ "$(jq -r '.head' <<<"$meta")" = "$REPO" ] || return 0
|
|
631
|
+
|
|
632
|
+
# GitHub computes mergeability asynchronously; poll until it is known (not null).
|
|
633
|
+
mergeable=""
|
|
634
|
+
for _ in 1 2 3 4 5 6; do
|
|
635
|
+
mergeable=$(gh api "repos/$REPO/pulls/$pr" --jq '.mergeable // "null"' 2>/dev/null || echo null)
|
|
636
|
+
[ "$mergeable" = "null" ] || break
|
|
637
|
+
sleep 5
|
|
638
|
+
done
|
|
639
|
+
# mergeable == false means a git-level conflict (mergeable_state "dirty").
|
|
640
|
+
if [ "$mergeable" != "false" ]; then
|
|
641
|
+
echo "PR #$pr mergeable=$mergeable; no conflict dispatch."
|
|
642
|
+
return 0
|
|
643
|
+
fi
|
|
644
|
+
|
|
645
|
+
# Only dispatch when CI has already concluded. A conflict while CI is still
|
|
646
|
+
# running is handled by the CI-completion route; dispatching now would make the
|
|
647
|
+
# gate read a 'pending' conclusion and prematurely ask for human review.
|
|
648
|
+
head_sha=$(gh api "repos/$REPO/pulls/$pr" --jq '.head.sha')
|
|
649
|
+
ci=$(gh api "repos/$REPO/actions/runs?head_sha=$head_sha&per_page=100" \
|
|
650
|
+
--jq '[.workflow_runs[]
|
|
651
|
+
| select(.name == "App: CI" and .status == "completed"
|
|
652
|
+
and (.conclusion == "success" or .conclusion == "failure"))]
|
|
653
|
+
| sort_by(.created_at) | last // empty')
|
|
654
|
+
if [ -z "$ci" ]; then
|
|
655
|
+
echo "PR #$pr conflicts but CI has not concluded; deferring to the CI-completion route."
|
|
656
|
+
return 0
|
|
657
|
+
fi
|
|
658
|
+
ci_conclusion=$(jq -r '.conclusion' <<<"$ci")
|
|
659
|
+
ci_run_id=$(jq -r '.id' <<<"$ci")
|
|
660
|
+
|
|
661
|
+
echo "PR #$pr is conflicting (CI $ci_conclusion); dispatching Merge Gate."
|
|
662
|
+
gh api "repos/$REPO/actions/workflows/work-router.yml/dispatches" \
|
|
663
|
+
--method POST \
|
|
664
|
+
-f ref="$DEFAULT_BRANCH" \
|
|
665
|
+
-f "inputs[operation]=merge-gate" \
|
|
666
|
+
-f "inputs[pr-number]=$pr" \
|
|
667
|
+
-f "inputs[ci-conclusion]=$ci_conclusion" \
|
|
668
|
+
-f "inputs[ci-run-id]=$ci_run_id"
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if [ "$ACTION" = "closed" ]; then
|
|
672
|
+
# The target branch just moved. Any previously-mergeable bot PR may now conflict.
|
|
673
|
+
# The just-merged PR is now closed and is skipped by maybe_dispatch's state check.
|
|
674
|
+
gh api "repos/$REPO/pulls?state=open&per_page=100" \
|
|
675
|
+
--jq '.[] | select(.user.type == "Bot" and .draft == false) | .number' |
|
|
676
|
+
while IFS= read -r pr; do
|
|
677
|
+
maybe_dispatch "$pr"
|
|
678
|
+
done
|
|
679
|
+
else
|
|
680
|
+
maybe_dispatch "$EVENT_PR"
|
|
681
|
+
fi
|
|
682
|
+
|
|
683
|
+
reconcile-bot-pr-runs:
|
|
684
|
+
needs: classify
|
|
685
|
+
if: needs.classify.outputs.route == 'reconcile-bot-pr-runs'
|
|
686
|
+
runs-on: ubuntu-latest
|
|
687
|
+
timeout-minutes: 5
|
|
688
|
+
permissions:
|
|
689
|
+
contents: read
|
|
690
|
+
actions: write
|
|
691
|
+
pull-requests: read
|
|
692
|
+
# the stale reservation sweep clears bot-working from parked issues
|
|
693
|
+
issues: write
|
|
694
|
+
env:
|
|
695
|
+
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
696
|
+
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
697
|
+
steps:
|
|
698
|
+
- name: Checkout for App token action
|
|
699
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
700
|
+
with:
|
|
701
|
+
persist-credentials: false
|
|
702
|
+
- name: Generate App token
|
|
703
|
+
id: app-token
|
|
704
|
+
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
|
705
|
+
with:
|
|
706
|
+
app-id: ${{ secrets.BOT_APP_ID }}
|
|
707
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
708
|
+
# Runs only the default-branch workflow and never checks out pull-request code.
|
|
709
|
+
# External users cannot impersonate github-actions[bot].
|
|
710
|
+
- name: Approve checks and dispatch Merge Gate for trusted bot pull requests
|
|
711
|
+
env:
|
|
712
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
713
|
+
REPO: ${{ github.repository }}
|
|
714
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
715
|
+
run: |
|
|
716
|
+
set -euo pipefail
|
|
717
|
+
|
|
718
|
+
# Stale reservation sweep. Labelling ten issues at once labels all ten immediately,
|
|
719
|
+
# because authorize-bot-work is keyed per issue, while implement-global lets exactly
|
|
720
|
+
# one run at a time. A queued run that is cancelled therefore leaves its issue
|
|
721
|
+
# carrying bot-working with no run and no pull request: parked, invisible, and not
|
|
722
|
+
# re-triggerable, because classify refuses an issue that is already reserved.
|
|
723
|
+
gh issue list --repo "$REPO" --label bot-working --state open --limit 100 \
|
|
724
|
+
--json number --jq '.[].number' |
|
|
725
|
+
while IFS= read -r issue; do
|
|
726
|
+
[ -n "$issue" ] || continue
|
|
727
|
+
|
|
728
|
+
live=$(gh api "repos/$REPO/actions/runs?per_page=100" \
|
|
729
|
+
--jq "[.workflow_runs[]
|
|
730
|
+
| select(.status == \"queued\" or .status == \"in_progress\" or .status == \"pending\" or .status == \"waiting\")
|
|
731
|
+
| select(.display_title | test(\"#${issue}\\b\"))] | length")
|
|
732
|
+
[ "${live:-0}" -eq 0 ] || continue
|
|
733
|
+
|
|
734
|
+
has_pr=$(gh pr list --repo "$REPO" --state open --json body \
|
|
735
|
+
--jq "[.[] | select((.body // \"\") | ascii_downcase | test(\"clos(e|es|ed) #${issue}\\b|fix(es|ed)? #${issue}\\b|resolves? #${issue}\\b\"))] | length")
|
|
736
|
+
[ "${has_pr:-0}" -eq 0 ] || continue
|
|
737
|
+
|
|
738
|
+
echo "Clearing a stale bot-working reservation on #$issue: no run, no pull request."
|
|
739
|
+
gh issue edit "$issue" --repo "$REPO" --remove-label bot-working
|
|
740
|
+
done
|
|
741
|
+
|
|
742
|
+
gh api "repos/$REPO/pulls?state=open&per_page=100" |
|
|
743
|
+
jq -r --arg repo "$REPO" '
|
|
744
|
+
.[]
|
|
745
|
+
| select(
|
|
746
|
+
(.user.login == "github-actions[bot]" or .user.login == "app/github-actions" or .user.login == "platform-devbox[bot]")
|
|
747
|
+
and .draft == false
|
|
748
|
+
and .head.repo.full_name == $repo
|
|
749
|
+
)
|
|
750
|
+
| [.number, .head.ref, .head.sha] | @tsv
|
|
751
|
+
' |
|
|
752
|
+
while IFS=$'\t' read -r pr_number branch head_sha; do
|
|
753
|
+
gh api "repos/$REPO/actions/runs?status=action_required&branch=$branch" \
|
|
754
|
+
--jq '.workflow_runs[]
|
|
755
|
+
| select(
|
|
756
|
+
.event == "pull_request"
|
|
757
|
+
and .name == "App: CI"
|
|
758
|
+
)
|
|
759
|
+
| .id' |
|
|
760
|
+
while IFS= read -r run_id; do
|
|
761
|
+
echo "Approving trusted bot run $run_id"
|
|
762
|
+
gh api "repos/$REPO/actions/runs/$run_id/approve" --method POST
|
|
763
|
+
done
|
|
764
|
+
|
|
765
|
+
ci=$(gh api "repos/$REPO/actions/runs?head_sha=$head_sha&per_page=100" \
|
|
766
|
+
--jq '[.workflow_runs[]
|
|
767
|
+
| select(
|
|
768
|
+
.name == "App: CI"
|
|
769
|
+
and .event == "pull_request"
|
|
770
|
+
and .status == "completed"
|
|
771
|
+
and (.conclusion == "success" or .conclusion == "failure")
|
|
772
|
+
)
|
|
773
|
+
] | sort_by(.created_at) | last // empty | [.id, .conclusion, .updated_at] | @tsv')
|
|
774
|
+
if [ -z "$ci" ]; then
|
|
775
|
+
continue
|
|
776
|
+
fi
|
|
777
|
+
|
|
778
|
+
IFS=$'\t' read -r ci_run_id ci_conclusion ci_finished <<<"$ci"
|
|
779
|
+
|
|
780
|
+
# A recorded verdict only counts when it is NEWER than the latest CI run.
|
|
781
|
+
# The old existence check made the gate one-shot per PR: after any verdict
|
|
782
|
+
# (say remediated), a later CI completion could never reopen the gate, so
|
|
783
|
+
# the fix-CI loop starved. ISO-8601 timestamps compare lexicographically.
|
|
784
|
+
latest_marker=$(gh api "repos/$REPO/issues/$pr_number/comments?per_page=100" \
|
|
785
|
+
--jq '[.[] | select(.body | contains("<!-- agent-merge-gate -->")) | .created_at] | max // empty')
|
|
786
|
+
linked_issue=$(gh pr view "$pr_number" --repo "$REPO" --json closingIssuesReferences --jq '.closingIssuesReferences[0].number // empty' 2>/dev/null || true)
|
|
787
|
+
if [ -n "$linked_issue" ]; then
|
|
788
|
+
issue_marker=$(gh api "repos/$REPO/issues/$linked_issue/comments?per_page=100" \
|
|
789
|
+
--jq '[.[] | select(.body | contains("<!-- agent-merge-gate -->")) | .created_at] | max // empty')
|
|
790
|
+
if [ -n "$issue_marker" ] && { [ -z "$latest_marker" ] || [ "$issue_marker" \> "$latest_marker" ]; }; then
|
|
791
|
+
latest_marker="$issue_marker"
|
|
792
|
+
fi
|
|
793
|
+
fi
|
|
794
|
+
if [ -n "$latest_marker" ] && [ "$latest_marker" \> "$ci_finished" ]; then
|
|
795
|
+
echo "Merge gate verdict ($latest_marker) is newer than CI ($ci_finished) for PR #$pr_number, skipping"
|
|
796
|
+
continue
|
|
797
|
+
fi
|
|
798
|
+
echo "Dispatching Merge Gate for PR #$pr_number ($ci_conclusion, run $ci_run_id)"
|
|
799
|
+
gh api "repos/$REPO/actions/workflows/work-router.yml/dispatches" \
|
|
800
|
+
--method POST \
|
|
801
|
+
-f ref="$DEFAULT_BRANCH" \
|
|
802
|
+
-f "inputs[operation]=merge-gate" \
|
|
803
|
+
-f "inputs[pr-number]=$pr_number" \
|
|
804
|
+
-f "inputs[ci-conclusion]=$ci_conclusion" \
|
|
805
|
+
-f "inputs[ci-run-id]=$ci_run_id"
|
|
806
|
+
done
|
|
807
|
+
|
|
808
|
+
audit-close:
|
|
809
|
+
needs: classify
|
|
810
|
+
if: needs.classify.outputs.route == 'audit-close'
|
|
811
|
+
runs-on: ubuntu-latest
|
|
812
|
+
timeout-minutes: 15
|
|
813
|
+
concurrency:
|
|
814
|
+
group: audit-close
|
|
815
|
+
cancel-in-progress: false
|
|
816
|
+
permissions:
|
|
817
|
+
contents: read
|
|
818
|
+
issues: write
|
|
819
|
+
steps:
|
|
820
|
+
- name: Checkout workflow actions
|
|
821
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
822
|
+
with:
|
|
823
|
+
persist-credentials: false
|
|
824
|
+
- uses: ./.github/actions/audit-close
|
|
825
|
+
with:
|
|
826
|
+
token: ${{ github.token }}
|
|
827
|
+
|
|
828
|
+
cleanup-artifacts:
|
|
829
|
+
needs: classify
|
|
830
|
+
if: needs.classify.outputs.route == 'cleanup-artifacts' && !github.event.repository.fork
|
|
831
|
+
runs-on: ubuntu-latest
|
|
832
|
+
timeout-minutes: 15
|
|
833
|
+
concurrency:
|
|
834
|
+
group: cleanup-artifacts
|
|
835
|
+
cancel-in-progress: false
|
|
836
|
+
permissions:
|
|
837
|
+
contents: read
|
|
838
|
+
actions: write
|
|
839
|
+
steps:
|
|
840
|
+
- name: Checkout workflow actions
|
|
841
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
842
|
+
with:
|
|
843
|
+
persist-credentials: false
|
|
844
|
+
- uses: ./.github/actions/cleanup-artifacts
|
|
845
|
+
with:
|
|
846
|
+
token: ${{ github.token }}
|
|
847
|
+
artifact-retention-days: ${{ vars.ARTIFACT_RETENTION_DAYS }}
|
|
848
|
+
|
|
849
|
+
validate:
|
|
850
|
+
needs: classify
|
|
851
|
+
if: needs.classify.outputs.route == 'validate'
|
|
852
|
+
runs-on: ubuntu-latest
|
|
853
|
+
timeout-minutes: 10
|
|
854
|
+
permissions:
|
|
855
|
+
contents: read
|
|
856
|
+
steps:
|
|
857
|
+
- name: Checkout workflow actions
|
|
858
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
859
|
+
with:
|
|
860
|
+
persist-credentials: false
|
|
861
|
+
- uses: ./.github/actions/verify-route-matrix
|
|
862
|
+
- uses: ./.github/actions/verify-composite-actions
|