@plainconceptsplatform/workflows 0.19.2 → 0.20.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/dist/catalog-installation.js +12 -1
- package/dist/index.js +0 -0
- package/dist/stack-defaults.js +16 -16
- package/dist/worker-env.js +14 -0
- package/loops/actions/add-issue-labels/action.yml +50 -50
- package/loops/actions/agent-output.cjs +17 -17
- package/loops/actions/apply-agent-bundle/action.yml +24 -24
- package/loops/actions/apply-agent-comments/action.yml +42 -42
- package/loops/actions/apply-agent-labels/action.yml +55 -55
- package/loops/actions/apply-agent-output/action.yml +108 -108
- package/loops/actions/assess-blast-radius/action.yml +148 -0
- package/loops/actions/assess-blast-radius/assess-blast-radius.sh +138 -0
- package/loops/actions/classify-route/action.yml +100 -100
- package/loops/actions/cleanup-artifacts/action.yml +91 -91
- package/loops/actions/close-agent-issues/action.yml +43 -43
- package/loops/actions/create-agent-issues/action.yml +52 -52
- package/loops/actions/create-issue-comment/action.yml +29 -29
- package/loops/actions/download-agent-output/action.yml +53 -53
- package/loops/actions/housekeeping/action.yml +55 -2
- package/loops/actions/link-pr-to-issue/action.yml +40 -40
- package/loops/actions/list-open-issues/action.yml +33 -33
- package/loops/actions/load-issue-context/action.yml +45 -45
- package/loops/actions/merge-agent-pr/action.yml +49 -49
- package/loops/actions/push-agent-branch/action.yml +45 -45
- package/loops/actions/remove-issue-labels/action.yml +37 -37
- package/loops/actions/update-agent-issues/action.yml +58 -58
- package/loops/actions/validate-merge-gate-output/action.yml +62 -40
- package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +147 -31
- package/loops/actions/validate-refine-output/action.yml +48 -44
- package/loops/actions/validate-refine-output/validate-refine-output.sh +15 -4
- package/loops/actions/validate-review-output/action.yml +35 -35
- package/loops/actions/validate-triage-output/action.yml +36 -36
- package/loops/actions/verify-composite-actions/action.yml +9 -9
- package/loops/actions/verify-refine-output/action.yml +9 -9
- package/loops/actions/verify-refine-output/verify-refine-output.sh +6 -1
- package/loops/actions/verify-route-matrix/action.yml +9 -9
- package/loops/actions/verify-route-matrix/verify-gate-metrics.mjs +51 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +329 -29
- package/loops/scripts/compile-agent-workflows.mjs +331 -331
- package/loops/templates/agentics/agentics-maintenance.yml +121 -121
- package/loops/templates/ci/app-ci-dotnet-next.yml +330 -330
- package/loops/templates/ci/app-ci-node-monorepo.yml +260 -260
- 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 +55 -49
- package/loops/templates/opencode/opencode.ci.json.md +59 -49
- package/loops/templates/release/github-release.yml +30 -30
- package/loops/workflows/agent-merge-gate.md +367 -148
- package/loops/workflows/agent-refine.md +60 -16
- package/loops/workflows/authorize-bot-work.yml +105 -105
- package/loops/workflows/shared/opencode-ci.md +206 -206
- package/loops/workflows/shared/platform-defaults.md +19 -19
- package/package.json +12 -11
|
@@ -24,6 +24,7 @@ env:
|
|
|
24
24
|
# re-running a decision produces the same decision. Created idempotently where it is applied.
|
|
25
25
|
STALLED_LABEL: stalled
|
|
26
26
|
REFINE_MARKER: "<!-- agent-refine -->"
|
|
27
|
+
DRAFT_MARKER: "<!-- agent-refine-draft -->"
|
|
27
28
|
INITIAL_MODE: first
|
|
28
29
|
RESPONSE_MODE: rerefine
|
|
29
30
|
MAX_SELF_QUESTIONS: "5"
|
|
@@ -46,6 +47,10 @@ description: |
|
|
|
46
47
|
Refines an issue into a user story, on a first pass or after the author has answered the
|
|
47
48
|
bot's questions. Replaces .loops/recipes/refine-loop.yaml.
|
|
48
49
|
|
|
50
|
+
The refined story is wrapped in the repository's own issue template when one matches.
|
|
51
|
+
When questions remain, the worker leaves that template-wrapped temporal draft on the
|
|
52
|
+
issue and asks every remaining question in a single batched comment.
|
|
53
|
+
|
|
49
54
|
Before writing the story, the agent explores the codebase per work unit (each bullet in a
|
|
50
55
|
bullet-list issue is its own unit), answering its own questions where the code can and
|
|
51
56
|
escalating only genuine business decisions to the author.
|
|
@@ -173,6 +178,7 @@ jobs:
|
|
|
173
178
|
with:
|
|
174
179
|
output-file: ${{ steps.output.outputs.output-file }}
|
|
175
180
|
marker: ${{ env.REFINE_MARKER }}
|
|
181
|
+
draft-marker: ${{ env.DRAFT_MARKER }}
|
|
176
182
|
comment-prefix: ${{ env.SAFE_OUTPUT_COMMENT_PREFIX }}
|
|
177
183
|
issue-number: ${{ inputs.issue-number }}
|
|
178
184
|
conclude:
|
|
@@ -472,7 +478,9 @@ timeout-minutes: 90
|
|
|
472
478
|
|
|
473
479
|
- On a `${{ env.INITIAL_MODE }}` pass, refine from scratch.
|
|
474
480
|
- On a `${{ env.RESPONSE_MODE }}` pass, incorporate only the supplied answers from the issue author or an
|
|
475
|
-
assignee. Do not use answers from other commenters.
|
|
481
|
+
assignee. Do not use answers from other commenters. The body may already hold the
|
|
482
|
+
temporal draft from the earlier pass: reuse what still holds, and resolve its pending
|
|
483
|
+
marks with the author's answers.
|
|
476
484
|
|
|
477
485
|
3. Explore before you write. Call skill("pc-plan-explore"); it owns the stance for this step.
|
|
478
486
|
|
|
@@ -516,8 +524,8 @@ timeout-minutes: 90
|
|
|
516
524
|
No "As a / I want / so that" form. No Given/When/Then. No Mermaid. Just the marker,
|
|
517
525
|
the summary, and the checklist.
|
|
518
526
|
|
|
519
|
-
Load `@humanizer` and prepare the replacement issue body, then go directly to step
|
|
520
|
-
(estimate). Skip steps 5
|
|
527
|
+
Load `@humanizer` and prepare the replacement issue body, then go directly to step 8
|
|
528
|
+
(estimate). Skip steps 5-7.
|
|
521
529
|
|
|
522
530
|
5. Before writing the story, verify coverage: list every work unit and confirm each one has
|
|
523
531
|
exploration findings concrete enough for acceptance criteria. If any unit is missing, go back
|
|
@@ -531,9 +539,36 @@ timeout-minutes: 90
|
|
|
531
539
|
Apply repository documentation and established conventions before finalizing the story.
|
|
532
540
|
Adhere to ${{ env.REPO_RULES }}.
|
|
533
541
|
|
|
534
|
-
6.
|
|
542
|
+
6. **Wrap the story in the repository's issue form.** The body people read must follow the
|
|
543
|
+
repository's own issue template when one exists; the story is the content, the template
|
|
544
|
+
is the shape.
|
|
545
|
+
|
|
546
|
+
Find the form first:
|
|
547
|
+
|
|
548
|
+
- List the YAML and Markdown forms under `.github/ISSUE_TEMPLATE/`, plus a legacy
|
|
549
|
+
`.github/issue_template.md` or a root `template.yml`. `config.yml` there only declares
|
|
550
|
+
contact links, which are not forms: ignore it.
|
|
551
|
+
- When a form filters by labels and the issue carries one of those labels, that form
|
|
552
|
+
wins. Otherwise use the repository's default form.
|
|
553
|
+
- When the repository has no form at all, keep the free-form story shape from step 5:
|
|
554
|
+
there is nothing to wrap around.
|
|
555
|
+
|
|
556
|
+
Then fill it:
|
|
557
|
+
|
|
558
|
+
- Draw every field's content from your exploration findings. Required fields always get
|
|
559
|
+
real content; optional fields only when you genuinely have something for them.
|
|
560
|
+
- The story narrative lands in the field that asks for it — proposal, description, or
|
|
561
|
+
what-happened, depending on the form.
|
|
562
|
+
- The Given/When/Then scenarios go into the form's acceptance-criteria field when it has
|
|
563
|
+
one; otherwise they stay a section of their own. The Mermaid diagram goes where it
|
|
564
|
+
reads best inside the filled form.
|
|
565
|
+
- The machine-readable lines the later steps add — split markers in step 9, estimate
|
|
566
|
+
lines in step 10 — always sit at the very top of the body, above the form's first
|
|
567
|
+
heading, so the workflow can read them whatever the form's shape.
|
|
535
568
|
|
|
536
|
-
7.
|
|
569
|
+
7. Load `@humanizer` and prepare the complete replacement issue body as valid Markdown.
|
|
570
|
+
|
|
571
|
+
8. **Estimate the story in points.** Use the Fibonacci scale, where one point is roughly one
|
|
537
572
|
human day of work for a developer who knows this codebase. Estimate the whole story: code,
|
|
538
573
|
tests, and the edge cases the acceptance criteria imply.
|
|
539
574
|
|
|
@@ -546,7 +581,7 @@ timeout-minutes: 90
|
|
|
546
581
|
Elapsed clock time is not evidence. A large change can land in minutes and a small one can
|
|
547
582
|
wait days for a human, so never reason from how long anything took.
|
|
548
583
|
|
|
549
|
-
|
|
584
|
+
9. **Split when the estimate is ${{ env.SPLIT_THRESHOLD }} or more.** An oversized story is the
|
|
550
585
|
single best predictor of a pull request that never lands.
|
|
551
586
|
|
|
552
587
|
First test whether it *can* split. A story splits when it contains slices that are each
|
|
@@ -555,7 +590,7 @@ timeout-minutes: 90
|
|
|
555
590
|
on its own cannot be verified.
|
|
556
591
|
|
|
557
592
|
**If it splits:** write between two and ${{ env.MAX_SPLIT_CHILDREN }} children. Each child is
|
|
558
|
-
a complete refined story in the same
|
|
593
|
+
a complete refined story wrapped in the same issue form, with its own
|
|
559
594
|
acceptance criteria, its own tests section, and its own estimate of 5 or less. Never write a
|
|
560
595
|
child estimated at 1: that is a fragment, so fold it into a sibling. Call `create_issue` once
|
|
561
596
|
per child, and in each child body include:
|
|
@@ -571,7 +606,7 @@ timeout-minutes: 90
|
|
|
571
606
|
single story and say so in one sentence in the body, under the estimate. An honest 8 is more
|
|
572
607
|
useful than three fake threes that each break the build.
|
|
573
608
|
|
|
574
|
-
|
|
609
|
+
10. **Record the estimate in every body you write**, parent and children alike, immediately below
|
|
575
610
|
the title line, as exactly these two lines:
|
|
576
611
|
|
|
577
612
|
```
|
|
@@ -582,12 +617,22 @@ timeout-minutes: 90
|
|
|
582
617
|
The visible line is for people and the marker is read by the workflow, which turns it into the
|
|
583
618
|
`sp-N` label. A body without the marker gets no estimate label at all.
|
|
584
619
|
|
|
585
|
-
|
|
620
|
+
11. Decide exactly one outcome:
|
|
586
621
|
|
|
587
622
|
Labels are workflow-owned state. Do not call `add_labels` or `remove_labels`.
|
|
588
623
|
|
|
589
624
|
**Questions remain.** You set aside one or more questions for the author that the codebase
|
|
590
|
-
could not answer. Leave the
|
|
625
|
+
could not answer. Leave the partial work visible: first call `update_issue` with a
|
|
626
|
+
temporal draft, then call `add_comment` once with the questions.
|
|
627
|
+
|
|
628
|
+
The temporal draft is the replacement body your path would have written — the filled
|
|
629
|
+
issue form from step 6 on the standard path, the marker, summary and checklist from
|
|
630
|
+
step 4a on the trivial path — holding everything you already established, with every
|
|
631
|
+
part the questions leave open marked `_pending — see questions below_`. Its very
|
|
632
|
+
first line is `${{ env.DRAFT_MARKER }}`; the next run replaces the draft wholesale
|
|
633
|
+
with the final body.
|
|
634
|
+
|
|
635
|
+
The comment carries:
|
|
591
636
|
1. `${{ env.REFINE_MARKER }}`
|
|
592
637
|
2. `${{ env.SAFE_OUTPUT_COMMENT_PREFIX }}`
|
|
593
638
|
3. `I have some questions about this issue. Please reply in one comment and I'll process your answers.`
|
|
@@ -597,10 +642,9 @@ timeout-minutes: 90
|
|
|
597
642
|
them is a domain expert, not an engineer.
|
|
598
643
|
|
|
599
644
|
**The story is complete.** You answered every exploration question yourself and none remain
|
|
600
|
-
for the author.
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
the whole body in that one call: it is the only thing this step has to get right.
|
|
645
|
+
for the author. Call `update_issue` first, with the wrapped replacement body, and wait
|
|
646
|
+
for it to come back. Send the whole body in that one call: it is the only thing this
|
|
647
|
+
step has to get right.
|
|
604
648
|
|
|
605
649
|
Only once that call has succeeded, call `add_comment`
|
|
606
650
|
with `${{ env.REFINE_MARKER }}`, then `${{ env.SAFE_OUTPUT_COMMENT_PREFIX }}`,
|
|
@@ -618,5 +662,5 @@ timeout-minutes: 90
|
|
|
618
662
|
seams. Call `create_issue` once per child, then `update_issue` on the parent with the
|
|
619
663
|
summary and the checklist, then `add_comment` with `${{ env.REFINE_MARKER }}`, then
|
|
620
664
|
`${{ env.SAFE_OUTPUT_COMMENT_PREFIX }}`, then one sentence naming the estimate you gave the
|
|
621
|
-
|
|
622
|
-
|
|
665
|
+
whole and how many children you wrote. The children carry the work forward; the parent stays
|
|
666
|
+
open as their tracker and is never implemented directly.
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/authorize-bot-work.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
-
# Human adds implement/refine/direct/feature label → validates permission → bot adds bot-working
|
|
3
|
-
# This ensures the bot is the actor for all agentic workflows.
|
|
4
|
-
#
|
|
5
|
-
# IMPORTANT: Only triggers for HUMAN actors. When the bot transitions refine→implement,
|
|
6
|
-
# it adds bot-working itself, so authorize-bot-work must not fire again.
|
|
7
|
-
name: "Authorize Bot Work"
|
|
8
|
-
|
|
9
|
-
run-name: "Authorizing: ${{ github.event.issue.title }} (#${{ github.event.issue.number }})"
|
|
10
|
-
|
|
11
|
-
on:
|
|
12
|
-
issues:
|
|
13
|
-
types: [labeled]
|
|
14
|
-
|
|
15
|
-
permissions:
|
|
16
|
-
contents: read
|
|
17
|
-
|
|
18
|
-
jobs:
|
|
19
|
-
authorize:
|
|
20
|
-
# Only trigger for work labels from HUMANS (not bots), and only if another bot run does not
|
|
21
|
-
# already own the issue.
|
|
22
|
-
#
|
|
23
|
-
# `review` is deliberately NOT excluded. It used to be, and that made the label a one-way
|
|
24
|
-
# door: the classifier refuses to route while `review` is set, so a person adding `refine`
|
|
25
|
-
# to a parked issue got no run, no comment and no error anywhere. Triage's own
|
|
26
|
-
# needs-maintainer verdict tells the maintainer to add `refine`, which could not work.
|
|
27
|
-
#
|
|
28
|
-
# A person adding a work label IS the human review the label was waiting for, so this
|
|
29
|
-
# workflow clears it below before handing the issue to the bot. The classifier's own guard
|
|
30
|
-
# stays as it is: it exists to stop the *bot* re-triggering itself, and by the time
|
|
31
|
-
# bot-working is added `review` is already gone.
|
|
32
|
-
if: >
|
|
33
|
-
(github.event.label.name == 'implement' ||
|
|
34
|
-
github.event.label.name == 'refine') &&
|
|
35
|
-
!contains(github.event.issue.labels.*.name, 'bot-working') &&
|
|
36
|
-
!endsWith(github.actor, '[bot]')
|
|
37
|
-
runs-on: ubuntu-latest
|
|
38
|
-
timeout-minutes: 5
|
|
39
|
-
concurrency:
|
|
40
|
-
group: authorize-${{ github.event.issue.number }}
|
|
41
|
-
cancel-in-progress: false
|
|
42
|
-
permissions:
|
|
43
|
-
contents: read
|
|
44
|
-
issues: write
|
|
45
|
-
steps:
|
|
46
|
-
- name: Check actor permission
|
|
47
|
-
id: check
|
|
48
|
-
env:
|
|
49
|
-
GH_TOKEN: ${{ github.token }}
|
|
50
|
-
ACTOR: ${{ github.actor }}
|
|
51
|
-
run: |
|
|
52
|
-
set -euo pipefail
|
|
53
|
-
|
|
54
|
-
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$ACTOR/permission" \
|
|
55
|
-
--jq '.permission' 2>/dev/null || echo "none")
|
|
56
|
-
|
|
57
|
-
case "$permission" in
|
|
58
|
-
admin | maintain | write)
|
|
59
|
-
echo "authorized=true" >> "$GITHUB_OUTPUT"
|
|
60
|
-
echo "::notice::$ACTOR has $permission permission - authorized"
|
|
61
|
-
;;
|
|
62
|
-
*)
|
|
63
|
-
echo "authorized=false" >> "$GITHUB_OUTPUT"
|
|
64
|
-
echo "::warning::$ACTOR has '$permission' permission - not authorized"
|
|
65
|
-
;;
|
|
66
|
-
esac
|
|
67
|
-
|
|
68
|
-
- name: Checkout for App token action
|
|
69
|
-
if: steps.check.outputs.authorized == 'true'
|
|
70
|
-
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
71
|
-
with:
|
|
72
|
-
persist-credentials: false
|
|
73
|
-
|
|
74
|
-
- name: Generate App token
|
|
75
|
-
if: steps.check.outputs.authorized == 'true'
|
|
76
|
-
id: app-token
|
|
77
|
-
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
78
|
-
with:
|
|
79
|
-
app-id: ${{ secrets.BOT_APP_ID }}
|
|
80
|
-
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
81
|
-
|
|
82
|
-
- name: Bot takes the issue over
|
|
83
|
-
if: steps.check.outputs.authorized == 'true'
|
|
84
|
-
env:
|
|
85
|
-
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
86
|
-
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
87
|
-
LABEL: ${{ github.event.label.name }}
|
|
88
|
-
run: |
|
|
89
|
-
set -euo pipefail
|
|
90
|
-
|
|
91
|
-
# Clear review FIRST. A person adding a work label is the human review the label was
|
|
92
|
-
# waiting for, and the classifier refuses to route an issue that still carries it, so
|
|
93
|
-
# removing it here is what makes the hand-off work at all. It also has to happen
|
|
94
|
-
# before bot-working, because that is the event the classifier reads: adding
|
|
95
|
-
# bot-working first would raise an event whose payload still shows review.
|
|
96
|
-
# --remove-label on an absent label is a no-op, so this is safe on a fresh issue.
|
|
97
|
-
# `stalled` goes with it: a person taking the issue on restarts the work, so the
|
|
98
|
-
# marker the janitor retries on must not survive into the new run.
|
|
99
|
-
gh issue edit "$ISSUE_NUMBER" --remove-label "review" --remove-label "stalled" 2>/dev/null ||
|
|
100
|
-
echo "::notice::no review or stalled label to clear on #$ISSUE_NUMBER"
|
|
101
|
-
|
|
102
|
-
echo "Adding bot-working label to issue #$ISSUE_NUMBER (triggered by $LABEL)"
|
|
103
|
-
gh issue edit "$ISSUE_NUMBER" --add-label "bot-working"
|
|
104
|
-
|
|
105
|
-
echo "::notice::bot-working label added - bot will now own the $LABEL workflow"
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/authorize-bot-work.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
+
# Human adds implement/refine/direct/feature label → validates permission → bot adds bot-working
|
|
3
|
+
# This ensures the bot is the actor for all agentic workflows.
|
|
4
|
+
#
|
|
5
|
+
# IMPORTANT: Only triggers for HUMAN actors. When the bot transitions refine→implement,
|
|
6
|
+
# it adds bot-working itself, so authorize-bot-work must not fire again.
|
|
7
|
+
name: "Authorize Bot Work"
|
|
8
|
+
|
|
9
|
+
run-name: "Authorizing: ${{ github.event.issue.title }} (#${{ github.event.issue.number }})"
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
issues:
|
|
13
|
+
types: [labeled]
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
authorize:
|
|
20
|
+
# Only trigger for work labels from HUMANS (not bots), and only if another bot run does not
|
|
21
|
+
# already own the issue.
|
|
22
|
+
#
|
|
23
|
+
# `review` is deliberately NOT excluded. It used to be, and that made the label a one-way
|
|
24
|
+
# door: the classifier refuses to route while `review` is set, so a person adding `refine`
|
|
25
|
+
# to a parked issue got no run, no comment and no error anywhere. Triage's own
|
|
26
|
+
# needs-maintainer verdict tells the maintainer to add `refine`, which could not work.
|
|
27
|
+
#
|
|
28
|
+
# A person adding a work label IS the human review the label was waiting for, so this
|
|
29
|
+
# workflow clears it below before handing the issue to the bot. The classifier's own guard
|
|
30
|
+
# stays as it is: it exists to stop the *bot* re-triggering itself, and by the time
|
|
31
|
+
# bot-working is added `review` is already gone.
|
|
32
|
+
if: >
|
|
33
|
+
(github.event.label.name == 'implement' ||
|
|
34
|
+
github.event.label.name == 'refine') &&
|
|
35
|
+
!contains(github.event.issue.labels.*.name, 'bot-working') &&
|
|
36
|
+
!endsWith(github.actor, '[bot]')
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
timeout-minutes: 5
|
|
39
|
+
concurrency:
|
|
40
|
+
group: authorize-${{ github.event.issue.number }}
|
|
41
|
+
cancel-in-progress: false
|
|
42
|
+
permissions:
|
|
43
|
+
contents: read
|
|
44
|
+
issues: write
|
|
45
|
+
steps:
|
|
46
|
+
- name: Check actor permission
|
|
47
|
+
id: check
|
|
48
|
+
env:
|
|
49
|
+
GH_TOKEN: ${{ github.token }}
|
|
50
|
+
ACTOR: ${{ github.actor }}
|
|
51
|
+
run: |
|
|
52
|
+
set -euo pipefail
|
|
53
|
+
|
|
54
|
+
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$ACTOR/permission" \
|
|
55
|
+
--jq '.permission' 2>/dev/null || echo "none")
|
|
56
|
+
|
|
57
|
+
case "$permission" in
|
|
58
|
+
admin | maintain | write)
|
|
59
|
+
echo "authorized=true" >> "$GITHUB_OUTPUT"
|
|
60
|
+
echo "::notice::$ACTOR has $permission permission - authorized"
|
|
61
|
+
;;
|
|
62
|
+
*)
|
|
63
|
+
echo "authorized=false" >> "$GITHUB_OUTPUT"
|
|
64
|
+
echo "::warning::$ACTOR has '$permission' permission - not authorized"
|
|
65
|
+
;;
|
|
66
|
+
esac
|
|
67
|
+
|
|
68
|
+
- name: Checkout for App token action
|
|
69
|
+
if: steps.check.outputs.authorized == 'true'
|
|
70
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
71
|
+
with:
|
|
72
|
+
persist-credentials: false
|
|
73
|
+
|
|
74
|
+
- name: Generate App token
|
|
75
|
+
if: steps.check.outputs.authorized == 'true'
|
|
76
|
+
id: app-token
|
|
77
|
+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
78
|
+
with:
|
|
79
|
+
app-id: ${{ secrets.BOT_APP_ID }}
|
|
80
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
81
|
+
|
|
82
|
+
- name: Bot takes the issue over
|
|
83
|
+
if: steps.check.outputs.authorized == 'true'
|
|
84
|
+
env:
|
|
85
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
86
|
+
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
87
|
+
LABEL: ${{ github.event.label.name }}
|
|
88
|
+
run: |
|
|
89
|
+
set -euo pipefail
|
|
90
|
+
|
|
91
|
+
# Clear review FIRST. A person adding a work label is the human review the label was
|
|
92
|
+
# waiting for, and the classifier refuses to route an issue that still carries it, so
|
|
93
|
+
# removing it here is what makes the hand-off work at all. It also has to happen
|
|
94
|
+
# before bot-working, because that is the event the classifier reads: adding
|
|
95
|
+
# bot-working first would raise an event whose payload still shows review.
|
|
96
|
+
# --remove-label on an absent label is a no-op, so this is safe on a fresh issue.
|
|
97
|
+
# `stalled` goes with it: a person taking the issue on restarts the work, so the
|
|
98
|
+
# marker the janitor retries on must not survive into the new run.
|
|
99
|
+
gh issue edit "$ISSUE_NUMBER" --remove-label "review" --remove-label "stalled" 2>/dev/null ||
|
|
100
|
+
echo "::notice::no review or stalled label to clear on #$ISSUE_NUMBER"
|
|
101
|
+
|
|
102
|
+
echo "Adding bot-working label to issue #$ISSUE_NUMBER (triggered by $LABEL)"
|
|
103
|
+
gh issue edit "$ISSUE_NUMBER" --add-label "bot-working"
|
|
104
|
+
|
|
105
|
+
echo "::notice::bot-working label added - bot will now own the $LABEL workflow"
|