@plainconceptsplatform/workflows 0.6.1 → 0.16.0
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 +105 -88
- package/dist/catalog-installation.d.ts +39 -2
- package/dist/catalog-installation.js +172 -109
- package/dist/index.js +128 -73
- package/dist/package-baseline.d.ts +25 -0
- package/dist/package-baseline.js +138 -0
- package/dist/route-processing.d.ts +0 -2
- package/dist/route-processing.js +22 -91
- package/dist/stack-defaults.js +18 -12
- package/dist/tui.js +27 -43
- package/dist/worker-env.d.ts +46 -0
- package/dist/worker-env.js +179 -0
- package/dist/workflow-catalog.d.ts +4 -2
- package/dist/workflow-catalog.js +5 -3
- package/loops/actions/add-issue-labels/action.yml +20 -0
- package/loops/actions/audit-close/action.yml +180 -128
- package/loops/actions/classify-route/classify-route.sh +8 -2
- package/loops/actions/housekeeping/action.yml +251 -0
- package/loops/actions/merge-agent-pr/action.yml +13 -0
- package/loops/actions/report-workflow-errors/action.yml +385 -0
- package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +13 -1
- package/loops/actions/validate-triage-output/action.yml +1 -1
- package/loops/actions/validate-triage-output/validate-triage-output.sh +9 -5
- package/loops/actions/verify-composite-actions/verify-composite-actions.sh +53 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +847 -170
- package/loops/templates/agentics/agentics-error-report.yml +97 -0
- package/loops/templates/opencode/opencode.ci.json +1 -1
- package/loops/workflows/agent-apply-review.md +452 -469
- package/loops/workflows/agent-audit.md +201 -213
- package/loops/workflows/agent-implement.md +616 -640
- package/loops/workflows/agent-merge-gate.md +830 -844
- package/loops/workflows/agent-refine.md +599 -633
- package/loops/workflows/agent-release.md +244 -258
- package/loops/workflows/agent-triage.md +476 -447
- package/loops/workflows/authorize-bot-work.yml +26 -6
- package/loops/workflows/work-router.yml +1185 -1038
- package/package.json +9 -8
- package/dist/action-validation.test.d.ts +0 -1
- package/dist/action-validation.test.js +0 -87
- package/dist/catalog-installation.test.d.ts +0 -1
- package/dist/catalog-installation.test.js +0 -485
- package/dist/catalog-listing.test.d.ts +0 -1
- package/dist/catalog-listing.test.js +0 -150
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -273
- package/dist/repository-inspection.test.d.ts +0 -1
- package/dist/repository-inspection.test.js +0 -77
- package/dist/route-processing.test.d.ts +0 -1
- package/dist/route-processing.test.js +0 -283
- package/dist/stack-defaults.test.d.ts +0 -1
- package/dist/stack-defaults.test.js +0 -266
- package/dist/tui.test.d.ts +0 -1
- package/dist/tui.test.js +0 -249
- package/dist/workflow-catalog.test.d.ts +0 -1
- package/dist/workflow-catalog.test.js +0 -29
- package/loops/actions/stale-recovery/action.yml +0 -288
- package/loops/actions/update-changelog/action.yml +0 -113
|
@@ -1,844 +1,830 @@
|
|
|
1
|
-
---
|
|
2
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-merge-gate.md. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
|
-
env:
|
|
4
|
-
VERIFY_COMMANDS: ""
|
|
5
|
-
REPO_RULES: "Make a risk-based merge decision for the selected bot pull request. Merge only when CI is green and no risk
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
the
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
description:
|
|
59
|
-
required:
|
|
60
|
-
type: string
|
|
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
|
-
- name:
|
|
190
|
-
if: needs.protected_changes.outputs.
|
|
191
|
-
uses:
|
|
192
|
-
with:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
- name:
|
|
373
|
-
if: needs.validate_output.outputs.outcome == '
|
|
374
|
-
uses: ./.github/actions/remove-issue-labels
|
|
375
|
-
with:
|
|
376
|
-
token: ${{ steps.app-token.outputs.token }}
|
|
377
|
-
issue-number: ${{ needs.subject.outputs.issue }}
|
|
378
|
-
labels:
|
|
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
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
**Check
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
**Check
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
classDef start fill:#ffffff,stroke:#172033,stroke-width:2px,color:#172033
|
|
833
|
-
classDef action fill:#eef0ff,stroke:#554cff,stroke-width:2px,color:#172033
|
|
834
|
-
classDef decision fill:#fff8e8,stroke:#c75b00,stroke-width:2px,color:#172033
|
|
835
|
-
classDef idle fill:#202c40,stroke:#738198,stroke-width:2px,color:#ffffff
|
|
836
|
-
classDef failure fill:#fff0f0,stroke:#ef2929,stroke-width:2px,color:#8b1a2a
|
|
837
|
-
classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
|
|
838
|
-
class gateStart start
|
|
839
|
-
class gateFacts,gateFix,gateRebase action
|
|
840
|
-
class gateSubject,gateCi,gateAssess,gateTrivial,gateConflict,gateProtected decision
|
|
841
|
-
class gateIdle,gateWait idle
|
|
842
|
-
class gateHuman failure
|
|
843
|
-
class gateMerge success
|
|
844
|
-
```
|
|
1
|
+
---
|
|
2
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-merge-gate.md. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
|
+
env:
|
|
4
|
+
VERIFY_COMMANDS: ""
|
|
5
|
+
REPO_RULES: "Make a risk-based merge decision for the selected bot pull request. Merge only when CI is green and no risk indicator is present. Do not merge protected file changes."
|
|
6
|
+
# The list that decides whether a machine merges without a human looking. It used to be a
|
|
7
|
+
# sentence inside REPO_RULES telling the agent to consult "the repository's guardrails or
|
|
8
|
+
# project documentation", which named no list at all and left the most consequential check in
|
|
9
|
+
# the pipeline resolving against nothing. Name the areas this repository will not auto-merge.
|
|
10
|
+
# One line: gh-aw joins a multi-line env value onto a single line when it compiles the lock.
|
|
11
|
+
RISK_INDICATORS: "Any diff touching authentication, authorization or session handling. Any change to a calculation or pricing engine, or to code handling money. Any database migration, or a change to an entity or schema. Any change to an audit or event log, or anything that could break its continuity. Any change to a public API contract or a shared library other repositories consume."
|
|
12
|
+
# Paths a bot may change but never merge on its own: an extended regular expression matched
|
|
13
|
+
# against every changed path in the pull request. The default names this stack's dependency
|
|
14
|
+
# and toolchain manifests plus everything under a dotted directory, and is wrong for a
|
|
15
|
+
# repository built on anything else, which is the failure worth knowing about: an unmatched
|
|
16
|
+
# list protects nothing and reports nothing. A match holds the merge for a human; it does not
|
|
17
|
+
# stop the agent repairing failed CI on the same files.
|
|
18
|
+
PROTECTED_PATHS: '^(\.|AGENTS\.md$|ARCHITECTURE\.md$|opencode\.jsonc$|package\.json$|pnpm-lock\.yaml$|Directory\.Packages\.props$|global\.json$)'
|
|
19
|
+
WORKING_LABEL: bot-working
|
|
20
|
+
IMPLEMENT_LABEL: implement
|
|
21
|
+
REVIEW_LABEL: review
|
|
22
|
+
# Marks a park the machine caused — a crash, a timeout, an empty output — as opposed to one it
|
|
23
|
+
# decided on. The janitor retries these after a while and never touches a decision park, because
|
|
24
|
+
# re-running a decision produces the same decision. Created idempotently where it is applied.
|
|
25
|
+
STALLED_LABEL: stalled
|
|
26
|
+
PR_PENDING_LABEL: pr-pending
|
|
27
|
+
GATE_MARKER: "<!-- agent-merge-gate -->"
|
|
28
|
+
ATTEMPT_MARKER: "<!-- agent-merge-gate-attempt -->"
|
|
29
|
+
MAX_ATTEMPTS: "6"
|
|
30
|
+
PARK_AT_ATTEMPT: "5"
|
|
31
|
+
ISSUE_CONTEXT_PATH: /tmp/gh-aw/agent/issue-context.json
|
|
32
|
+
GH_AW_ALLOWED_BOTS: "platform-devbox[bot],github-actions[bot]"
|
|
33
|
+
GIT_AUTHOR_NAME: "github-actions[bot]"
|
|
34
|
+
GIT_AUTHOR_EMAIL: "github-actions[bot]@users.noreply.github.com"
|
|
35
|
+
GIT_COMMITTER_NAME: "github-actions[bot]"
|
|
36
|
+
GIT_COMMITTER_EMAIL: "github-actions[bot]@users.noreply.github.com"
|
|
37
|
+
description: |
|
|
38
|
+
Decides what happens to a bot-authored pull request once CI has reported: merge when the
|
|
39
|
+
risk assessment is clean, hand to a human when it is not, fix CI when it failed. Called by
|
|
40
|
+
the Work Router; does not trigger on public events.
|
|
41
|
+
|
|
42
|
+
The router supplies the CI conclusion and run ID as facts, so there is no polling and no
|
|
43
|
+
timeout branch. The conclusion is read from the inputs instead of filtered at the trigger.
|
|
44
|
+
|
|
45
|
+
name: "Agent: Merge Gate"
|
|
46
|
+
|
|
47
|
+
# Router-only worker. The Work Router owns triggers, classification, and rung 1-2 checks.
|
|
48
|
+
# This workflow receives the classified inputs and runs rung 3+.
|
|
49
|
+
imports:
|
|
50
|
+
- github/gh-aw/.github/workflows/shared/opencode.md@v0.87.5
|
|
51
|
+
- shared/platform-defaults.md
|
|
52
|
+
- shared/opencode-ci.md
|
|
53
|
+
|
|
54
|
+
on:
|
|
55
|
+
workflow_call:
|
|
56
|
+
inputs:
|
|
57
|
+
pr-number:
|
|
58
|
+
description: Pull request number to gate.
|
|
59
|
+
required: true
|
|
60
|
+
type: string
|
|
61
|
+
linked-issue:
|
|
62
|
+
description: Issue number the pull request closes. May be empty.
|
|
63
|
+
required: false
|
|
64
|
+
type: string
|
|
65
|
+
ci-conclusion:
|
|
66
|
+
description: CI conclusion (success, failure, action_required, cancelled, etc.).
|
|
67
|
+
required: true
|
|
68
|
+
type: string
|
|
69
|
+
ci-run-id:
|
|
70
|
+
description: CI workflow run ID for fetching failing logs.
|
|
71
|
+
required: false
|
|
72
|
+
type: string
|
|
73
|
+
attempts_so_far:
|
|
74
|
+
description: Failed gate attempts already made against this CI verdict. Parked when it reaches the cap.
|
|
75
|
+
required: false
|
|
76
|
+
type: string
|
|
77
|
+
default: '0'
|
|
78
|
+
# gh-aw folds the top-level `if:` below into the generated activation job but does not carry
|
|
79
|
+
# the jobs that `if:` reads into activation's `needs`: only prompt-referenced custom jobs with
|
|
80
|
+
# no `needs:` of their own are hoisted (subject). protected_changes needs subject, so without
|
|
81
|
+
# this entry activation read needs.protected_changes.outputs.requires_review before
|
|
82
|
+
# protected_changes had started; the value was '' and the clause was always true
|
|
83
|
+
# (Pliny-Bot run 34042143350: activation finished 18 s before protected_changes began).
|
|
84
|
+
needs: [protected_changes]
|
|
85
|
+
|
|
86
|
+
# Rung 4. Router has classified the event; identify-gate-subject validates PR ownership,
|
|
87
|
+
# resolves the closing issue, and confirms the CI verdict.
|
|
88
|
+
# A custom job, not `on.steps`, because the prompt and the precompute step need these
|
|
89
|
+
# values and `on.steps` outputs do not reach the agent job.
|
|
90
|
+
jobs:
|
|
91
|
+
subject:
|
|
92
|
+
runs-on: agents-arc
|
|
93
|
+
permissions:
|
|
94
|
+
contents: read
|
|
95
|
+
issues: read
|
|
96
|
+
pull-requests: read
|
|
97
|
+
actions: read
|
|
98
|
+
outputs:
|
|
99
|
+
found: ${{ steps.subject.outputs.found }}
|
|
100
|
+
pr: ${{ steps.subject.outputs.pr }}
|
|
101
|
+
issue: ${{ steps.subject.outputs.issue }}
|
|
102
|
+
conclusion: ${{ steps.subject.outputs.conclusion }}
|
|
103
|
+
run-id: ${{ steps.subject.outputs.run-id }}
|
|
104
|
+
review_blocked: ${{ steps.review.outputs.review_blocked }}
|
|
105
|
+
steps:
|
|
106
|
+
- name: Checkout workflow actions
|
|
107
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
108
|
+
with:
|
|
109
|
+
persist-credentials: false
|
|
110
|
+
- name: Identify the pull request, its issue, and the CI verdict
|
|
111
|
+
id: subject
|
|
112
|
+
uses: ./.github/actions/identify-gate-subject
|
|
113
|
+
with:
|
|
114
|
+
token: ${{ github.token }}
|
|
115
|
+
pr-number: ${{ inputs.pr-number }}
|
|
116
|
+
ci-conclusion: ${{ inputs.ci-conclusion }}
|
|
117
|
+
linked-issue: ${{ inputs.linked-issue }}
|
|
118
|
+
require-label: ${{ env.IMPLEMENT_LABEL }}
|
|
119
|
+
- name: Block a pull request with requested changes
|
|
120
|
+
id: review
|
|
121
|
+
env:
|
|
122
|
+
GH_TOKEN: ${{ github.token }}
|
|
123
|
+
REPO: ${{ github.repository }}
|
|
124
|
+
PR: ${{ steps.subject.outputs.pr }}
|
|
125
|
+
run: |
|
|
126
|
+
set -euo pipefail
|
|
127
|
+
# A transient API failure must not kill the gate (the reconcile cron can push the
|
|
128
|
+
# token into secondary rate limits). Three tries, then default to not blocked; the
|
|
129
|
+
# agent re-reads the review state itself before any merge.
|
|
130
|
+
decision=""
|
|
131
|
+
for _ in 1 2 3; do
|
|
132
|
+
if decision=$(gh pr view "$PR" --repo "$REPO" --json reviewDecision --jq '.reviewDecision // ""'); then
|
|
133
|
+
break
|
|
134
|
+
fi
|
|
135
|
+
sleep 5
|
|
136
|
+
done
|
|
137
|
+
echo "review_blocked=$([ "$decision" = 'CHANGES_REQUESTED' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
138
|
+
|
|
139
|
+
protected_changes:
|
|
140
|
+
needs: subject
|
|
141
|
+
if: needs.subject.outputs.found == 'true'
|
|
142
|
+
runs-on: agents-arc
|
|
143
|
+
permissions:
|
|
144
|
+
pull-requests: read
|
|
145
|
+
outputs:
|
|
146
|
+
requires_review: ${{ steps.files.outputs.requires_review }}
|
|
147
|
+
files: ${{ steps.files.outputs.files }}
|
|
148
|
+
# The decision, computed once. A protected path holds the merge for a human, but it must
|
|
149
|
+
# not stop the agent repairing failed CI on those same files: blocking there strands the
|
|
150
|
+
# pull request with nobody able to fix it. That pair of conditions used to be restated at
|
|
151
|
+
# eight call sites, five of them steps of one job, and the trap table documents it because
|
|
152
|
+
# it has already been got wrong. `holds_review` is the only place it is decided now.
|
|
153
|
+
holds_review: ${{ steps.files.outputs.requires_review == 'true' && needs.subject.outputs.conclusion != 'failure' }}
|
|
154
|
+
steps:
|
|
155
|
+
- name: Require review for protected pull request files
|
|
156
|
+
id: files
|
|
157
|
+
env:
|
|
158
|
+
GH_TOKEN: ${{ github.token }}
|
|
159
|
+
REPO: ${{ github.repository }}
|
|
160
|
+
PR: ${{ needs.subject.outputs.pr }}
|
|
161
|
+
PROTECTED_PATHS: ${{ env.PROTECTED_PATHS }}
|
|
162
|
+
run: |
|
|
163
|
+
set -euo pipefail
|
|
164
|
+
files=$(gh api --paginate "repos/$REPO/pulls/$PR/files?per_page=100" --jq '.[].filename')
|
|
165
|
+
protected=$(printf '%s\n' "$files" | grep -E "$PROTECTED_PATHS" || true)
|
|
166
|
+
|
|
167
|
+
if [ -n "$protected" ]; then
|
|
168
|
+
echo "requires_review=true" >> "$GITHUB_OUTPUT"
|
|
169
|
+
{
|
|
170
|
+
echo 'files<<EOF'
|
|
171
|
+
printf '%s\n' "$protected"
|
|
172
|
+
echo EOF
|
|
173
|
+
} >> "$GITHUB_OUTPUT"
|
|
174
|
+
else
|
|
175
|
+
echo "requires_review=false" >> "$GITHUB_OUTPUT"
|
|
176
|
+
echo "files=" >> "$GITHUB_OUTPUT"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
review_required:
|
|
180
|
+
needs: [subject, protected_changes]
|
|
181
|
+
# gh-aw makes the agent depend on custom jobs. Keep this job successful when
|
|
182
|
+
# there are no protected files instead of skipping it and blocking remediation.
|
|
183
|
+
if: always() && needs.subject.outputs.found == 'true'
|
|
184
|
+
runs-on: agents-arc
|
|
185
|
+
permissions:
|
|
186
|
+
contents: read
|
|
187
|
+
issues: write
|
|
188
|
+
steps:
|
|
189
|
+
- name: Checkout workflow actions
|
|
190
|
+
if: needs.protected_changes.outputs.holds_review == 'true'
|
|
191
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
192
|
+
with:
|
|
193
|
+
persist-credentials: false
|
|
194
|
+
- name: Create bot token
|
|
195
|
+
if: needs.protected_changes.outputs.holds_review == 'true'
|
|
196
|
+
id: app-token
|
|
197
|
+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
198
|
+
with:
|
|
199
|
+
client-id: ${{ secrets.BOT_APP_ID }}
|
|
200
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
201
|
+
# Only the reservation comes off. pr-pending says a pull request for this issue is open
|
|
202
|
+
# and waiting, which is still true when the gate hands it to a human, so taking it off
|
|
203
|
+
# here left a board where three issues with three open pull requests looked like they
|
|
204
|
+
# had none. The merge path is the one place the label stops being true.
|
|
205
|
+
- name: Release the issue
|
|
206
|
+
if: needs.protected_changes.outputs.holds_review == 'true'
|
|
207
|
+
uses: ./.github/actions/remove-issue-labels
|
|
208
|
+
with:
|
|
209
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
210
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
211
|
+
labels: ${{ env.WORKING_LABEL }}
|
|
212
|
+
- name: Flag human review
|
|
213
|
+
if: needs.protected_changes.outputs.holds_review == 'true'
|
|
214
|
+
uses: ./.github/actions/add-issue-labels
|
|
215
|
+
with:
|
|
216
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
217
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
218
|
+
labels: ${{ env.REVIEW_LABEL }}
|
|
219
|
+
- name: Explain the merge hold
|
|
220
|
+
if: needs.protected_changes.outputs.holds_review == 'true'
|
|
221
|
+
uses: ./.github/actions/create-issue-comment
|
|
222
|
+
with:
|
|
223
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
224
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
225
|
+
body: |
|
|
226
|
+
${{ env.GATE_MARKER }}
|
|
227
|
+
PR #${{ needs.subject.outputs.pr }} changes protected files and cannot be auto-merged.
|
|
228
|
+
The `review` label is set: a human must merge this PR manually.
|
|
229
|
+
|
|
230
|
+
Protected files:
|
|
231
|
+
${{ needs.protected_changes.outputs.files }}
|
|
232
|
+
|
|
233
|
+
**Verdict:** review
|
|
234
|
+
|
|
235
|
+
reserve:
|
|
236
|
+
needs: subject
|
|
237
|
+
if: needs.subject.outputs.found == 'true'
|
|
238
|
+
runs-on: agents-arc
|
|
239
|
+
permissions:
|
|
240
|
+
contents: read
|
|
241
|
+
issues: write
|
|
242
|
+
pull-requests: read
|
|
243
|
+
outputs:
|
|
244
|
+
has_conflicts: ${{ steps.conflicts.outputs.has_conflicts || 'false' }}
|
|
245
|
+
steps:
|
|
246
|
+
- name: Checkout workflow actions
|
|
247
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
248
|
+
with:
|
|
249
|
+
persist-credentials: false
|
|
250
|
+
- name: Create bot token
|
|
251
|
+
id: app-token
|
|
252
|
+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
253
|
+
with:
|
|
254
|
+
client-id: ${{ secrets.BOT_APP_ID }}
|
|
255
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
256
|
+
- name: Check for merge conflicts
|
|
257
|
+
id: conflicts
|
|
258
|
+
env:
|
|
259
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
260
|
+
REPO: ${{ github.repository }}
|
|
261
|
+
PR: ${{ needs.subject.outputs.pr }}
|
|
262
|
+
run: |
|
|
263
|
+
set -euo pipefail
|
|
264
|
+
mergeable=$(gh pr view "$PR" --repo "$REPO" --json mergeable --jq '.mergeable')
|
|
265
|
+
if [ "$mergeable" = "CONFLICTING" ]; then
|
|
266
|
+
echo "has_conflicts=true" >> "$GITHUB_OUTPUT"
|
|
267
|
+
else
|
|
268
|
+
echo "has_conflicts=false" >> "$GITHUB_OUTPUT"
|
|
269
|
+
fi
|
|
270
|
+
# First attempt only. Retries are recorded by the incomplete job's attempt comment, and
|
|
271
|
+
# every App comment is a router event: one issue collected sixteen of these in a day.
|
|
272
|
+
- name: Comment on issue - problems found, solving them
|
|
273
|
+
if: needs.subject.outputs.conclusion == 'failure' && (inputs.attempts_so_far || '0') == '0'
|
|
274
|
+
uses: ./.github/actions/create-issue-comment
|
|
275
|
+
with:
|
|
276
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
277
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
278
|
+
body: |
|
|
279
|
+
Problems found in PR #${{ needs.subject.outputs.pr }}. ${{ steps.conflicts.outputs.has_conflicts == 'true' && 'Merge conflicts detected.' || 'CI failed.' }}
|
|
280
|
+
Bot is working on fixing it.
|
|
281
|
+
validate_output:
|
|
282
|
+
needs: [activation, subject, agent, safe_outputs]
|
|
283
|
+
if: >
|
|
284
|
+
always() &&
|
|
285
|
+
needs.agent.result == 'success' &&
|
|
286
|
+
needs.safe_outputs.result == 'success'
|
|
287
|
+
runs-on: agents-arc
|
|
288
|
+
permissions:
|
|
289
|
+
contents: read
|
|
290
|
+
outputs:
|
|
291
|
+
valid: ${{ steps.validate.outputs.valid }}
|
|
292
|
+
outcome: ${{ steps.validate.outputs.outcome }}
|
|
293
|
+
steps:
|
|
294
|
+
- name: Checkout workflow actions
|
|
295
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
296
|
+
with:
|
|
297
|
+
persist-credentials: false
|
|
298
|
+
- name: Download agent output
|
|
299
|
+
id: output
|
|
300
|
+
uses: ./.github/actions/download-agent-output
|
|
301
|
+
with:
|
|
302
|
+
artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
|
|
303
|
+
- name: Validate merge-gate outcome
|
|
304
|
+
id: validate
|
|
305
|
+
uses: ./.github/actions/validate-merge-gate-output
|
|
306
|
+
with:
|
|
307
|
+
output-file: ${{ steps.output.outputs.output-file }}
|
|
308
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
309
|
+
ci-conclusion: ${{ needs.subject.outputs.conclusion }}
|
|
310
|
+
conclude:
|
|
311
|
+
needs: [activation, subject, protected_changes, agent, safe_outputs, validate_output]
|
|
312
|
+
if: >
|
|
313
|
+
needs.agent.result == 'success' &&
|
|
314
|
+
needs.safe_outputs.result == 'success' &&
|
|
315
|
+
needs.validate_output.outputs.valid == 'true' &&
|
|
316
|
+
(needs.protected_changes.outputs.requires_review != 'true' || needs.validate_output.outputs.outcome != 'merge')
|
|
317
|
+
runs-on: agents-arc
|
|
318
|
+
permissions:
|
|
319
|
+
contents: write
|
|
320
|
+
issues: write
|
|
321
|
+
pull-requests: write
|
|
322
|
+
steps:
|
|
323
|
+
- name: Create bot token
|
|
324
|
+
id: app-token
|
|
325
|
+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
326
|
+
with:
|
|
327
|
+
client-id: ${{ secrets.BOT_APP_ID }}
|
|
328
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
329
|
+
- name: Checkout repository
|
|
330
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
331
|
+
with:
|
|
332
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
333
|
+
fetch-depth: 0
|
|
334
|
+
- name: Verify pull request closes the source issue
|
|
335
|
+
continue-on-error: true
|
|
336
|
+
uses: ./.github/actions/link-pr-to-issue
|
|
337
|
+
with:
|
|
338
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
339
|
+
pr-number: ${{ needs.subject.outputs.pr }}
|
|
340
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
341
|
+
- name: Apply agent output
|
|
342
|
+
uses: ./.github/actions/apply-agent-output
|
|
343
|
+
with:
|
|
344
|
+
artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
|
|
345
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
346
|
+
push-to-branch: 'true'
|
|
347
|
+
apply-labels: 'false'
|
|
348
|
+
# GITHUB_TOKEN on purpose: an App-token comment on a pull request is an issue_comment
|
|
349
|
+
# event, and GITHUB_TOKEN raises none. The full assessment lives on the issue, where the
|
|
350
|
+
# lifecycle is; this is what a reviewer opening the pull request sees. Carrying the
|
|
351
|
+
# marker and the Verdict line makes the router's verdict detection independent of
|
|
352
|
+
# whether the model remembered the marker.
|
|
353
|
+
- name: Show the verdict on the pull request
|
|
354
|
+
uses: ./.github/actions/create-issue-comment
|
|
355
|
+
with:
|
|
356
|
+
token: ${{ github.token }}
|
|
357
|
+
issue-number: ${{ needs.subject.outputs.pr }}
|
|
358
|
+
body: |
|
|
359
|
+
${{ env.GATE_MARKER }}
|
|
360
|
+
**Verdict:** ${{ needs.validate_output.outputs.outcome }} (CI concluded ${{ needs.subject.outputs.conclusion }}).
|
|
361
|
+
Full assessment on the linked issue: #${{ needs.subject.outputs.issue }}. [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
362
|
+
- name: Merge approved pull request
|
|
363
|
+
if: needs.validate_output.outputs.outcome == 'merge'
|
|
364
|
+
env:
|
|
365
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
366
|
+
REPO: ${{ github.repository }}
|
|
367
|
+
PR: ${{ needs.subject.outputs.pr }}
|
|
368
|
+
run: |
|
|
369
|
+
set -euo pipefail
|
|
370
|
+
head_sha=$(gh pr view "$PR" --repo "$REPO" --json headRefOid --jq '.headRefOid')
|
|
371
|
+
gh pr merge "$PR" --repo "$REPO" --squash --match-head-commit "$head_sha"
|
|
372
|
+
- name: Release remediated issue
|
|
373
|
+
if: needs.validate_output.outputs.outcome == 'remediated'
|
|
374
|
+
uses: ./.github/actions/remove-issue-labels
|
|
375
|
+
with:
|
|
376
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
377
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
378
|
+
labels: ${{ env.WORKING_LABEL }}
|
|
379
|
+
- name: Flag review outcome
|
|
380
|
+
if: needs.validate_output.outputs.outcome == 'review'
|
|
381
|
+
uses: ./.github/actions/add-issue-labels
|
|
382
|
+
with:
|
|
383
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
384
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
385
|
+
labels: ${{ env.REVIEW_LABEL }}
|
|
386
|
+
# The reservation only. The pull request is still open and still waiting, so pr-pending
|
|
387
|
+
# stays until the merge path below retires it.
|
|
388
|
+
- name: Release review outcome
|
|
389
|
+
if: needs.validate_output.outputs.outcome == 'review'
|
|
390
|
+
uses: ./.github/actions/remove-issue-labels
|
|
391
|
+
with:
|
|
392
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
393
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
394
|
+
labels: ${{ env.WORKING_LABEL }}
|
|
395
|
+
- name: Clear merged issue labels
|
|
396
|
+
if: needs.validate_output.outputs.outcome == 'merge'
|
|
397
|
+
uses: ./.github/actions/remove-issue-labels
|
|
398
|
+
with:
|
|
399
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
400
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
401
|
+
labels: |
|
|
402
|
+
${{ env.IMPLEMENT_LABEL }}
|
|
403
|
+
${{ env.WORKING_LABEL }}
|
|
404
|
+
${{ env.REVIEW_LABEL }}
|
|
405
|
+
${{ env.PR_PENDING_LABEL }}
|
|
406
|
+
incomplete:
|
|
407
|
+
needs: [subject, protected_changes, agent, safe_outputs, validate_output]
|
|
408
|
+
if: >
|
|
409
|
+
always() &&
|
|
410
|
+
needs.subject.outputs.found == 'true' &&
|
|
411
|
+
needs.protected_changes.outputs.holds_review != 'true' &&
|
|
412
|
+
(
|
|
413
|
+
needs.agent.result != 'success' ||
|
|
414
|
+
needs.safe_outputs.result != 'success' ||
|
|
415
|
+
needs.validate_output.outputs.valid != 'true'
|
|
416
|
+
)
|
|
417
|
+
runs-on: agents-arc
|
|
418
|
+
permissions:
|
|
419
|
+
contents: read
|
|
420
|
+
issues: write
|
|
421
|
+
steps:
|
|
422
|
+
- name: Checkout workflow actions
|
|
423
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
424
|
+
with:
|
|
425
|
+
persist-credentials: false
|
|
426
|
+
- name: Create bot token
|
|
427
|
+
id: app-token
|
|
428
|
+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
429
|
+
with:
|
|
430
|
+
client-id: ${{ secrets.BOT_APP_ID }}
|
|
431
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
432
|
+
# The attempt record comes first. The belt bounds its retries by counting attempt comments
|
|
433
|
+
# newer than the CI verdict, not by labels; releasing the labels before the record existed
|
|
434
|
+
# meant a failure in either step below un-reserved the issue with nothing to count, and the
|
|
435
|
+
# belt re-dispatched the same crash every cycle. The steps stay sequential on purpose: an
|
|
436
|
+
# always() release after a failed park would strip bot-working from an issue that was
|
|
437
|
+
# meant to be parked with review.
|
|
438
|
+
# attempts_so_far is a workflow_call input and arrives as '' when the caller passes an
|
|
439
|
+
# empty expression, declared default or not; fromJson('') is a hard failure, so the empty
|
|
440
|
+
# case reads as 0.
|
|
441
|
+
- name: Report the failed attempt
|
|
442
|
+
if: fromJson(inputs.attempts_so_far || '0') < fromJson(env.PARK_AT_ATTEMPT)
|
|
443
|
+
uses: ./.github/actions/create-issue-comment
|
|
444
|
+
with:
|
|
445
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
446
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
447
|
+
body: |
|
|
448
|
+
${{ env.ATTEMPT_MARKER }}
|
|
449
|
+
Attempt ${{ inputs.attempts_so_far || '0' }} of ${{ env.MAX_ATTEMPTS }} on PR #${{ needs.subject.outputs.pr }} ended without an outcome.
|
|
450
|
+
The issue keeps `implement`; the merge belt will retry.
|
|
451
|
+
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
452
|
+
- name: Report the exhausted attempt budget
|
|
453
|
+
if: fromJson(inputs.attempts_so_far || '0') >= fromJson(env.PARK_AT_ATTEMPT)
|
|
454
|
+
uses: ./.github/actions/create-issue-comment
|
|
455
|
+
with:
|
|
456
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
457
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
458
|
+
body: |
|
|
459
|
+
${{ env.ATTEMPT_MARKER }}
|
|
460
|
+
Attempt ${{ inputs.attempts_so_far || '0' }} of ${{ env.MAX_ATTEMPTS }} on PR #${{ needs.subject.outputs.pr }} ended without an outcome.
|
|
461
|
+
The attempt budget for this CI verdict is exhausted. The review label is set: a human must take over.
|
|
462
|
+
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
463
|
+
- name: Park the issue for a human
|
|
464
|
+
if: fromJson(inputs.attempts_so_far || '0') >= fromJson(env.PARK_AT_ATTEMPT)
|
|
465
|
+
uses: ./.github/actions/add-issue-labels
|
|
466
|
+
with:
|
|
467
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
468
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
469
|
+
labels: |-
|
|
470
|
+
${{ env.REVIEW_LABEL }}
|
|
471
|
+
${{ env.STALLED_LABEL }}
|
|
472
|
+
# The reservation only. A failed attempt does not close the pull request, so pr-pending
|
|
473
|
+
# is still true and the board should keep saying so.
|
|
474
|
+
- name: Release the issue
|
|
475
|
+
uses: ./.github/actions/remove-issue-labels
|
|
476
|
+
with:
|
|
477
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
478
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
479
|
+
labels: ${{ env.WORKING_LABEL }}
|
|
480
|
+
|
|
481
|
+
agent:
|
|
482
|
+
# The top-level guard reads both outputs. GitHub Actions does not make a
|
|
483
|
+
# dependency's dependencies available through `needs` transitively.
|
|
484
|
+
needs: [subject, protected_changes]
|
|
485
|
+
if: always() && needs.protected_changes.outputs.holds_review != 'true' && needs.subject.outputs.review_blocked != 'true'
|
|
486
|
+
|
|
487
|
+
if: always() && needs.subject.outputs.found == 'true' && needs.protected_changes.outputs.holds_review != 'true' && needs.subject.outputs.review_blocked != 'true'
|
|
488
|
+
|
|
489
|
+
runs-on: agents-arc
|
|
490
|
+
runs-on-slim: agents-arc
|
|
491
|
+
|
|
492
|
+
secrets:
|
|
493
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
494
|
+
|
|
495
|
+
engine:
|
|
496
|
+
id: opencode
|
|
497
|
+
version: "1.2.14"
|
|
498
|
+
env:
|
|
499
|
+
OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
|
|
500
|
+
|
|
501
|
+
model: openai/glm-5-3
|
|
502
|
+
|
|
503
|
+
max-turns: 300
|
|
504
|
+
max-turn-cache-misses: 3000
|
|
505
|
+
max-ai-credits: 5000
|
|
506
|
+
|
|
507
|
+
permissions: read-all
|
|
508
|
+
|
|
509
|
+
# push-to-pull-request-branch with target "*" cannot reach a branch the shallow clone
|
|
510
|
+
# does not have.
|
|
511
|
+
checkout:
|
|
512
|
+
fetch: ["*"]
|
|
513
|
+
fetch-depth: 0
|
|
514
|
+
|
|
515
|
+
# Rung 3. The diff is what the risk assessment reads, and the failing logs are what a fix
|
|
516
|
+
# starts from. Both are known from the inputs, so neither costs the agent a turn.
|
|
517
|
+
steps:
|
|
518
|
+
# gh-aw checks out the router's ref. Its own "Checkout PR branch" step runs only when the event
|
|
519
|
+
# carries a pull request, which a router dispatch does not, so the agent would start on main.
|
|
520
|
+
# apply-agent-output fast-forwards origin/<branch> to the bundle tip and refuses anything else,
|
|
521
|
+
# and gh-aw builds that bundle from what the agent committed on top of the checkout; both need
|
|
522
|
+
# the agent to start on the branch it pushes to.
|
|
523
|
+
- name: Check out the pull request branch
|
|
524
|
+
env:
|
|
525
|
+
GH_TOKEN: ${{ github.token }}
|
|
526
|
+
REPO: ${{ github.repository }}
|
|
527
|
+
PR: ${{ needs.subject.outputs.pr }}
|
|
528
|
+
run: |
|
|
529
|
+
set -euo pipefail
|
|
530
|
+
branch=$(gh pr view "$PR" --repo "$REPO" --json headRefName --jq '.headRefName')
|
|
531
|
+
git switch --track "origin/$branch" 2>/dev/null || git switch "$branch"
|
|
532
|
+
echo "On $(git branch --show-current) at $(git rev-parse --short HEAD)"
|
|
533
|
+
# Every merge into the default branch invalidates every other open pull request that added a
|
|
534
|
+
# changelog entry, because they all insert at the top of the same list. The conflict is real
|
|
535
|
+
# and never interesting: both entries belong, newest first. Without a driver it costs a model
|
|
536
|
+
# run per sibling pull request, and it recurs on every merge while more than one is in flight.
|
|
537
|
+
# A consumer opts in by naming its own changelog in .gitattributes with `merge=changelog`;
|
|
538
|
+
# repositories that do not are unaffected, because git only calls a driver a path asks for.
|
|
539
|
+
- name: Teach git how to merge a changelog
|
|
540
|
+
run: |
|
|
541
|
+
set -euo pipefail
|
|
542
|
+
git config merge.changelog.name "newest-first changelog entries"
|
|
543
|
+
git config merge.changelog.driver "node $GITHUB_WORKSPACE/scripts/merge-changelog.mjs %O %A %B"
|
|
544
|
+
- name: Load the issue context
|
|
545
|
+
uses: ./.github/actions/load-issue-context
|
|
546
|
+
with:
|
|
547
|
+
token: ${{ github.token }}
|
|
548
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
549
|
+
output-path: ${{ env.ISSUE_CONTEXT_PATH }}
|
|
550
|
+
- name: Fetch the diff and any failing job logs
|
|
551
|
+
env:
|
|
552
|
+
GH_TOKEN: ${{ github.token }}
|
|
553
|
+
REPO: ${{ github.repository }}
|
|
554
|
+
PR: ${{ needs.subject.outputs.pr }}
|
|
555
|
+
RUN_ID: ${{ needs.subject.outputs.run-id }}
|
|
556
|
+
CONCLUSION: ${{ needs.subject.outputs.conclusion }}
|
|
557
|
+
run: |
|
|
558
|
+
set -euo pipefail
|
|
559
|
+
mkdir -p /tmp/gh-aw/agent
|
|
560
|
+
gh pr diff "$PR" --repo "$REPO" > /tmp/gh-aw/agent/diff.patch
|
|
561
|
+
gh pr view "$PR" --repo "$REPO" --json title,body,files,additions,deletions \
|
|
562
|
+
> /tmp/gh-aw/agent/pr.json
|
|
563
|
+
if [ "$CONCLUSION" = "failure" ] && [ -n "$RUN_ID" ]; then
|
|
564
|
+
gh run view "$RUN_ID" --repo "$REPO" --log-failed \
|
|
565
|
+
> /tmp/gh-aw/agent/failed-logs.txt 2>/dev/null || \
|
|
566
|
+
echo "logs unavailable" > /tmp/gh-aw/agent/failed-logs.txt
|
|
567
|
+
gh run view "$RUN_ID" --repo "$REPO" --json jobs \
|
|
568
|
+
--jq '[.jobs[] | select(.conclusion == "failure") | {name, conclusion}]' \
|
|
569
|
+
> /tmp/gh-aw/agent/failed-jobs.json
|
|
570
|
+
elif [ "$CONCLUSION" = "failure" ]; then
|
|
571
|
+
echo "[]" > /tmp/gh-aw/agent/failed-jobs.json
|
|
572
|
+
echo "CI run ID unavailable, cannot fetch logs" > /tmp/gh-aw/agent/failed-logs.txt
|
|
573
|
+
fi
|
|
574
|
+
|
|
575
|
+
safe-outputs:
|
|
576
|
+
# Path B. Without this, gh-aw's own safe_outputs job writes as well as the conclude job, and
|
|
577
|
+
# it runs first: it pushed a flattened, single-parent commit with GITHUB_TOKEN while conclude
|
|
578
|
+
# was still waiting, so the agent's merge commit was lost, the pull request stayed
|
|
579
|
+
# conflicting, and GITHUB_TOKEN raises no events, so no CI ran on the new head and the belt
|
|
580
|
+
# stalled (Pliny-Bot run 34051821011). Staged runs everything and writes nothing; conclude
|
|
581
|
+
# applies the bundle and the comment with the App token, which does start CI.
|
|
582
|
+
staged: true
|
|
583
|
+
# A failed run is already a red run. An issue per failure buries the real backlog
|
|
584
|
+
# under noise nobody closes.
|
|
585
|
+
report-failure-as-issue: false
|
|
586
|
+
threat-detection: false
|
|
587
|
+
# target "*" because these workers are dispatched, not triggered by the pull request:
|
|
588
|
+
# the default "triggering" target has no pull request in context and rejects the push
|
|
589
|
+
# with "requires pull request context", so the agent's fix is computed and discarded.
|
|
590
|
+
push-to-pull-request-branch:
|
|
591
|
+
target: "*"
|
|
592
|
+
required-title-prefix: "[bot] "
|
|
593
|
+
# A failed bot PR may already contain protected files. Permit a verified repair push,
|
|
594
|
+
# but protected_changes still prevents the later green-CI cycle from auto-merging it.
|
|
595
|
+
protected-files: allowed
|
|
596
|
+
add-comment:
|
|
597
|
+
target: "*"
|
|
598
|
+
|
|
599
|
+
# The fleet is two machines, so this clock is also how long a stuck run can hold half of it.
|
|
600
|
+
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
601
|
+
# made every worker equally expensive to hang. These numbers are per worker: enough headroom
|
|
602
|
+
# for a slow gateway on the work it actually does, and not four hours for a run that reads one
|
|
603
|
+
# issue. Turns remain the guard against a confused agent looping; for a custom model the credit
|
|
604
|
+
# ceiling is models.dev fallback pricing and guards nothing.
|
|
605
|
+
#
|
|
606
|
+
# Reads CI failure evidence and may fix, verify and re-push, so it can do implement's work on a smaller diff.
|
|
607
|
+
timeout-minutes: 120
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
1. You are gating pull request **#${{ needs.subject.outputs.pr }}**, which closes issue
|
|
611
|
+
**#${{ needs.subject.outputs.issue }}**. CI concluded
|
|
612
|
+
**${{ needs.subject.outputs.conclusion }}**.
|
|
613
|
+
|
|
614
|
+
It has already been confirmed that this is an open pull request we authored, that it closes
|
|
615
|
+
an issue, and that the issue carries `implement`. Do not re-check any of that, and do not
|
|
616
|
+
poll for checks: the conclusion above is the answer.
|
|
617
|
+
|
|
618
|
+
You are on the pull request branch. Never rebase, reset, amend or otherwise rewrite
|
|
619
|
+
history: the workflow applies your commits as a bundle with a fast-forward-only push and
|
|
620
|
+
discards anything that is not a descendant of the branch tip. The
|
|
621
|
+
`push_to_pull_request_branch` tool's own description recommends rebasing; in this
|
|
622
|
+
repository that advice is wrong. Merge, commit, and let the workflow push.
|
|
623
|
+
|
|
624
|
+
2. Read `${{ env.ISSUE_CONTEXT_PATH }}`. It contains the issue body and its discussion. When
|
|
625
|
+
running `/repo-verify`, the acceptance criteria there define what the implementation must
|
|
626
|
+
satisfy.
|
|
627
|
+
|
|
628
|
+
3. Branch on the conclusion.
|
|
629
|
+
|
|
630
|
+
First check the issue context for `<!-- complexity: trivial -->`.
|
|
631
|
+
|
|
632
|
+
**If the trivial marker is present AND CI conclusion is success:**
|
|
633
|
+
Skip the full assessment (step 5). Emit a minimal assessment table with all checks marked
|
|
634
|
+
✅ and the note "Trivial change, CI green — deep risk review skipped." Then proceed directly
|
|
635
|
+
to step 8 (merge verdict).
|
|
636
|
+
|
|
637
|
+
**If the trivial marker is absent OR CI is not success:**
|
|
638
|
+
Follow the normal branching below.
|
|
639
|
+
|
|
640
|
+
- **success** → step 4, then step 5 (full assessment).
|
|
641
|
+
- **action_required** → CI did not run because the workflow needs approval.
|
|
642
|
+
Emit the assessment table with ❌ on CI Status and note that a maintainer must approve
|
|
643
|
+
the pending run. Select the `review` verdict.
|
|
644
|
+
- **failure** → step 4, then step 6 (CI remediation).
|
|
645
|
+
- **cancelled, timed_out, or anything else** → Emit the assessment table with ❌ on CI
|
|
646
|
+
Status and select the `review` verdict. A cancelled or unknown run is not evidence of
|
|
647
|
+
anything.
|
|
648
|
+
|
|
649
|
+
Follow repository documentation and established conventions when assessing or remediating
|
|
650
|
+
the pull request. Protect secrets, do not bypass checks, and keep remediation focused.
|
|
651
|
+
Adhere to ${{ env.REPO_RULES }}.
|
|
652
|
+
|
|
653
|
+
4. Read the diff and PR metadata. Read `/tmp/gh-aw/agent/diff.patch` in full and
|
|
654
|
+
`/tmp/gh-aw/agent/pr.json` for the shape of the change. If CI failed, also read
|
|
655
|
+
`/tmp/gh-aw/agent/failed-jobs.json` and `/tmp/gh-aw/agent/failed-logs.txt`.
|
|
656
|
+
|
|
657
|
+
These files are the factual basis for every check below. Do not guess — cite what you read.
|
|
658
|
+
|
|
659
|
+
4b. **Merge conflict when CI is green.** If the conclusion is `success` and
|
|
660
|
+
`has_conflicts` is `true` (current value: `${{ needs.reserve.outputs.has_conflicts }}`),
|
|
661
|
+
resolve the conflict before assessing risk. You are already on the PR branch.
|
|
662
|
+
Merge `origin/${{ github.event.repository.default_branch }}` into it
|
|
663
|
+
(`git merge origin/${{ github.event.repository.default_branch }}`), resolve every
|
|
664
|
+
conflict deliberately, commit the merge, and run the verification commands below. Do not
|
|
665
|
+
use `--ours`, `--theirs`, or a blanket conflict-marker deletion without reviewing the
|
|
666
|
+
intended behavior from both sides, and never rebase: the push is fast-forward only.
|
|
667
|
+
|
|
668
|
+
Scope verification to the files the merge touched: pass changed file paths to
|
|
669
|
+
lint/format tools instead of running them repository-wide (see step 6's scoped
|
|
670
|
+
verification guidance).
|
|
671
|
+
|
|
672
|
+
```
|
|
673
|
+
${{ env.VERIFY_COMMANDS }}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Push the merged branch using `push_to_pull_request_branch` (pr_number: ${{ needs.subject.outputs.pr }},
|
|
677
|
+
branch: the current PR branch), then emit the `add_comment` with
|
|
678
|
+
**Verdict:** remediated. CI will re-run on the updated branch and the merge gate
|
|
679
|
+
will be triggered again — the next cycle will see a clean, conflict-free PR and can
|
|
680
|
+
make a proper merge or review decision.
|
|
681
|
+
|
|
682
|
+
If the merge cannot be completed or the conflicts are genuinely ambiguous, select the `review`
|
|
683
|
+
verdict instead and explain which conflicts could not be resolved safely.
|
|
684
|
+
|
|
685
|
+
If the conclusion is `success` and `has_conflicts` is `false`, skip this step and
|
|
686
|
+
proceed to step 5.
|
|
687
|
+
|
|
688
|
+
5. Run each of these 10 checks. For each, determine a status and a short detail line.
|
|
689
|
+
|
|
690
|
+
**Check 1 — CI Status.** What did CI conclude? Success means all required checks passed.
|
|
691
|
+
Failure means at least one job failed. Action required means a workflow needs approval.
|
|
692
|
+
Flag any non-success conclusion.
|
|
693
|
+
|
|
694
|
+
**Check 2 — Auth & Security.** Does the diff touch authentication, authorization, secrets,
|
|
695
|
+
credentials, or security boundaries? Flag any change to auth middleware, permission checks,
|
|
696
|
+
token issuance, or security-related config.
|
|
697
|
+
|
|
698
|
+
**Check 3 — API & Contracts.** Does the diff change a public API or a published package's
|
|
699
|
+
contract? Flag changes to endpoint signatures, DTO shapes, exported interfaces, or
|
|
700
|
+
serialization formats that could break consumers.
|
|
701
|
+
|
|
702
|
+
**Check 4 — Tests.** Does the diff delete, weaken, or lower a threshold in a test? Flag
|
|
703
|
+
removed assertions, skipped tests, lowered coverage bars, or deleted test files.
|
|
704
|
+
|
|
705
|
+
**Check 5 — CI/CD & Workflow files.** Does the diff change CI, CD, or workflow files?
|
|
706
|
+
Flag changes to `.github/workflows/`, Dockerfiles, deployment scripts, or infrastructure
|
|
707
|
+
configuration.
|
|
708
|
+
|
|
709
|
+
**Check 6 — Protected files.** Does the diff change a protected file? This is normally
|
|
710
|
+
handled before you run, but never merge one if it reaches this gate. Flag any match against
|
|
711
|
+
the repository's protected file list.
|
|
712
|
+
|
|
713
|
+
**Check 7 — Scope.** Is the diff size consistent with what the issue implied? Compare the
|
|
714
|
+
number of files changed and lines added/removed against the complexity the issue described.
|
|
715
|
+
Flag if the diff is materially larger or smaller than expected.
|
|
716
|
+
|
|
717
|
+
**Check 8 — Repository risk indicators.** Does the diff touch any of these?
|
|
718
|
+
${{ env.RISK_INDICATORS }}
|
|
719
|
+
|
|
720
|
+
Name the specific indicator you matched. A match is not a defect, it is a reason this
|
|
721
|
+
pull request needs a person, so do not argue it away because the change looks correct.
|
|
722
|
+
|
|
723
|
+
**Check 9 — Mergeability.** Can the PR be merged cleanly? The value is
|
|
724
|
+
`${{ needs.reserve.outputs.has_conflicts }}`. If conflicts exist, this is ❌ but not a
|
|
725
|
+
blocking verdict — proceed to remediation (step 6). If no conflicts, ✅.
|
|
726
|
+
|
|
727
|
+
**Check 10 — Confidence.** Are you confident in the merge decision? Low confidence is
|
|
728
|
+
itself a flag. If you are unsure about the impact of the change, mark ⚠️ and explain what
|
|
729
|
+
is uncertain. A human should review when confidence is low.
|
|
730
|
+
|
|
731
|
+
6. **CI failed** → read `/tmp/gh-aw/agent/failed-jobs.json` and
|
|
732
|
+
`/tmp/gh-aw/agent/failed-logs.txt`, which are already on disk. Load only skills required to
|
|
733
|
+
fix the actual cause. Run these verification commands before a push. Do not weaken a test,
|
|
734
|
+
disable a check, or push an unverified guess.
|
|
735
|
+
|
|
736
|
+
**Empty failure evidence is not a reason to ask for review.** If `failed-jobs.json` is `[]`
|
|
737
|
+
or the logs say they were unavailable, then no CI run judged this head. That is the normal
|
|
738
|
+
state of a conflicting pull request: GitHub cannot build `refs/pull/N/merge` while the
|
|
739
|
+
conflict lasts, so no `pull_request` CI can run on it and there is nothing to read. The
|
|
740
|
+
conflict is the failure to fix. Resolve it as described above, push, and select
|
|
741
|
+
`remediated`; CI runs on the result and the next cycle gets a real verdict. Select `review`
|
|
742
|
+
for missing evidence only when `has_conflicts` is `false`, because then there is genuinely
|
|
743
|
+
nothing to act on.
|
|
744
|
+
|
|
745
|
+
**Scoped verification.** The commands below are the full suite. This runner has limited
|
|
746
|
+
memory, and a whole-repo lint or build can be killed mid-run. Scope verification to the
|
|
747
|
+
files you actually changed first, and only escalate to the full suite when the scoped run
|
|
748
|
+
passes and you are still unsure:
|
|
749
|
+
- Lint/format (biome, eslint, prettier, ruff, etc.): pass the changed file paths as
|
|
750
|
+
arguments so the tool checks only those files (e.g. `pnpm exec biome check <files>`),
|
|
751
|
+
never the whole repository.
|
|
752
|
+
- Build: prefer building only the project(s) containing the changed files; use the full
|
|
753
|
+
solution build only when the change crosses project boundaries.
|
|
754
|
+
- Tests: run the test project covering the changed files; run the full suite only when
|
|
755
|
+
the change is cross-cutting.
|
|
756
|
+
If verification of exactly the CI-failing job is what you need, reproduce just that job's
|
|
757
|
+
command, not the entire pipeline.
|
|
758
|
+
|
|
759
|
+
A PR that already contains protected files still requires remediation. You may include those
|
|
760
|
+
files in the verified repair push, but the next green-CI cycle will require human review and
|
|
761
|
+
must not auto-merge the PR.
|
|
762
|
+
|
|
763
|
+
**If `has_conflicts` is `true` (current value: `${{ needs.reserve.outputs.has_conflicts }}`):** You are already on the PR branch. Resolve the conflict;
|
|
764
|
+
it is not a reason to hand the PR to a human. Merge `origin/${{ github.event.repository.default_branch }}`
|
|
765
|
+
into the current branch, resolve every conflict deliberately, stage the resolutions, and
|
|
766
|
+
commit the merge. Then run verification and push the resulting branch update. Do not use
|
|
767
|
+
`--ours`, `--theirs`, or a blanket conflict-marker deletion without reviewing the intended
|
|
768
|
+
behavior from both sides.
|
|
769
|
+
|
|
770
|
+
```
|
|
771
|
+
${{ env.VERIFY_COMMANDS }}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
Propose `push_to_pull_request_branch` (pr_number: ${{ needs.subject.outputs.pr }}, branch:
|
|
775
|
+
the current PR branch), then select the `remediated` verdict. CI will run again and trigger
|
|
776
|
+
you again with the new result.
|
|
777
|
+
|
|
778
|
+
Before pushing, run the project's lint fix command (e.g. `pnpm lint:fix` or
|
|
779
|
+
`pnpm exec biome check --write <changed-files>`) to auto-format. If lint:fix is not
|
|
780
|
+
available, fix formatting manually. Never push code with lint errors.
|
|
781
|
+
|
|
782
|
+
If you cannot fix it after a concrete repair attempt, or the logs show you have already tried on this same head commit,
|
|
783
|
+
stop looping: select the `review` verdict and explain the failure and what you tried. A human
|
|
784
|
+
decides from there.
|
|
785
|
+
|
|
786
|
+
7. Decide the verdict based on the assessment table:
|
|
787
|
+
|
|
788
|
+
- **All checks ✅ → `merge`.** The PR is safe to merge. CI is green, no risk indicators
|
|
789
|
+
triggered, tests are intact, scope matches, mergeability is clean.
|
|
790
|
+
- **Any check ⚠️ or ❌ (except CI failure) → `review`.** Do not merge. Explain exactly which
|
|
791
|
+
check tripped, why, and what a reviewer should look at. Leave `implement` in place: the
|
|
792
|
+
work is not finished until a human merges it.
|
|
793
|
+
- **CI failed and you fixed it → `remediated`.** You pushed a verified fix and CI will
|
|
794
|
+
re-run.
|
|
795
|
+
- **CI failed and you cannot fix it → `review`.** Explain the failure and what you tried.
|
|
796
|
+
|
|
797
|
+
Never merge with administrator privileges and never bypass a required check. If the merge
|
|
798
|
+
is refused, that refusal is the answer: select `review` and leave it for a human.
|
|
799
|
+
|
|
800
|
+
8. Emit exactly one `add_comment` targeting issue `${{ needs.subject.outputs.issue }}` with:
|
|
801
|
+
1. `${{ env.GATE_MARKER }}`
|
|
802
|
+
2. A heading: `## Merge gate decision for PR #${{ needs.subject.outputs.pr }}`
|
|
803
|
+
3. A structured assessment table with all 10 check results
|
|
804
|
+
4. A one-line detail per check (what was found and why it passed or flagged)
|
|
805
|
+
5. A line `**Verdict:** merge`, `**Verdict:** review`, or `**Verdict:** remediated`
|
|
806
|
+
|
|
807
|
+
The workflow applies comments, labels, merges, and closures with the App token. Do not call
|
|
808
|
+
any tools except the one optional `push_to_pull_request_branch` for a verified CI repair
|
|
809
|
+
and this one `add_comment`.
|
|
810
|
+
|
|
811
|
+
Format the checks as a table with status indicators:
|
|
812
|
+
|
|
813
|
+
```
|
|
814
|
+
### Assessment
|
|
815
|
+
|
|
816
|
+
# Check Result
|
|
817
|
+
1 CI Status ✅ Success / ❌ Failure: [job name] / ⚠️ Action required
|
|
818
|
+
2 Auth & Security ✅ No changes / ⚠️ Touched: [area]
|
|
819
|
+
3 API & Contracts ✅ No changes / ⚠️ Changed: [area]
|
|
820
|
+
4 Tests ✅ Not weakened / ⚠️ Weakened: [file]
|
|
821
|
+
5 CI/CD & Workflow ✅ No changes / ⚠️ Changed: [file]
|
|
822
|
+
6 Protected files ✅ None touched / ❌ Touched: [file]
|
|
823
|
+
7 Scope ✅ Appropriately scoped / ⚠️ [too large/small: reason]
|
|
824
|
+
8 Risk indicators ✅ None triggered / ⚠️ Triggered: [indicator]
|
|
825
|
+
9 Mergeability ✅ Clean / ⚠️ Conflicts
|
|
826
|
+
10 Confidence ✅ High / ⚠️ Low: [reason]
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
Then a line `**Verdict:** merge` / `**Verdict:** review` / `**Verdict:** remediated`
|
|
830
|
+
|