@plainconceptsplatform/workflows 0.4.32 → 0.4.34
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 +1 -1
- package/dist/action-validation.test.d.ts +1 -0
- package/dist/action-validation.test.js +84 -0
- package/dist/catalog-installation.js +30 -2
- package/dist/catalog-installation.test.js +17 -0
- package/dist/catalog-listing.js +6 -4
- package/dist/catalog-listing.test.js +15 -2
- package/dist/index.js +2 -1
- package/dist/index.test.js +7 -7
- package/dist/route-processing.test.js +12 -4
- package/dist/tui.test.js +8 -8
- package/dist/workflow-catalog.d.ts +3 -2
- package/dist/workflow-catalog.js +6 -0
- package/dist/workflow-catalog.test.js +1 -1
- package/loops/actions/classify-route/action.yml +3 -0
- package/loops/actions/classify-route/classify-route.sh +38 -3
- package/loops/actions/load-issue-context/action.yml +2 -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-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 +32 -2
- package/loops/templates/issues/bug_report.yml +109 -0
- package/loops/templates/issues/feature_request.yml +75 -0
- package/loops/workflows/agent-apply-review.md +89 -19
- package/loops/workflows/agent-audit.md +2 -2
- package/loops/workflows/agent-direct.md +14 -2
- package/loops/workflows/agent-implement.md +56 -16
- package/loops/workflows/agent-merge-gate.md +159 -60
- package/loops/workflows/agent-propose.md +1 -1
- package/loops/workflows/agent-refine.md +45 -6
- package/loops/workflows/agent-triage.md +439 -0
- package/loops/workflows/authorize-bot-work.yml +2 -1
- package/loops/workflows/work-router.yml +89 -8
- package/package.json +44 -43
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-merge-gate-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Validate merge-gate output
|
|
3
|
+
description: Verify agent_output.json contains a usable merge-gate verdict before any state changes.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
issue-number:
|
|
9
|
+
description: Issue number that the merge-gate outcome must target.
|
|
10
|
+
required: true
|
|
11
|
+
ci-conclusion:
|
|
12
|
+
description: CI conclusion supplied to the merge gate.
|
|
13
|
+
required: true
|
|
14
|
+
outputs:
|
|
15
|
+
valid:
|
|
16
|
+
description: Whether the output contains a usable merge-gate comment with a verdict.
|
|
17
|
+
value: ${{ steps.validate.outputs.valid }}
|
|
18
|
+
outcome:
|
|
19
|
+
description: "Deterministic outcome: merge, review, remediated, or invalid."
|
|
20
|
+
value: ${{ steps.validate.outputs.outcome }}
|
|
21
|
+
runs:
|
|
22
|
+
using: composite
|
|
23
|
+
steps:
|
|
24
|
+
- name: Validate merge-gate outcome
|
|
25
|
+
id: validate
|
|
26
|
+
shell: bash
|
|
27
|
+
env:
|
|
28
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
29
|
+
CI_CONCLUSION: ${{ inputs.ci-conclusion }}
|
|
30
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
31
|
+
run: |
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
|
|
34
|
+
outcome="$(bash "${{ github.action_path }}/validate-merge-gate-output.sh" "$OUTPUT_FILE" "$ISSUE_NUMBER" "$CI_CONCLUSION")"
|
|
35
|
+
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
36
|
+
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
37
|
+
|
|
38
|
+
if [ "$outcome" = 'invalid' ]; then
|
|
39
|
+
echo "::warning::Agent output has no usable merge-gate outcome. It will not be applied."
|
|
40
|
+
fi
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
|
+
# Print the deterministic merge-gate outcome: merge, review, remediated, or invalid.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
output_file="$1"
|
|
8
|
+
issue_number="$2"
|
|
9
|
+
ci_conclusion="$3"
|
|
10
|
+
|
|
11
|
+
if [ ! -f "$output_file" ] || ! jq -e '.items | arrays' "$output_file" >/dev/null 2>&1; then
|
|
12
|
+
echo invalid
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# The agent emits exactly one comment on the source issue. Its verdict tells the
|
|
17
|
+
# workflow which App-token state transition to perform.
|
|
18
|
+
jq -r --arg issue "$issue_number" --arg conclusion "$ci_conclusion" '
|
|
19
|
+
.items as $items
|
|
20
|
+
| ($items
|
|
21
|
+
| [.[] | select(.type == "add_comment" and (.item_number | tostring) == $issue and (.body | type == "string"))]
|
|
22
|
+
| map(.body |
|
|
23
|
+
if test("\\*\\*Verdict:\\*\\*\\s*(merge|review|remediated)"; "i") then
|
|
24
|
+
capture("\\*\\*Verdict:\\*\\*\\s*(?<v>merge|review|remediated)"; "i").v | ascii_downcase
|
|
25
|
+
else empty end
|
|
26
|
+
)
|
|
27
|
+
| .[0] // "invalid") as $outcome
|
|
28
|
+
| ([$items[] | select(.type == "push_to_pull_request_branch")] | length) as $pushes
|
|
29
|
+
| if $outcome == "merge" and $conclusion == "success" and $pushes == 0 then "merge"
|
|
30
|
+
elif $outcome == "remediated" and $conclusion == "failure" and $pushes == 1 then "remediated"
|
|
31
|
+
elif $outcome == "review" and $pushes == 0 then "review"
|
|
32
|
+
else "invalid"
|
|
33
|
+
end
|
|
34
|
+
' "$output_file"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-review-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Validate review output
|
|
3
|
+
description: Verify an apply-review outcome accounts for every unresolved review thread.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
pr-number:
|
|
9
|
+
description: Pull request receiving the review feedback.
|
|
10
|
+
required: true
|
|
11
|
+
review-threads-file:
|
|
12
|
+
description: JSON file containing unresolved review threads.
|
|
13
|
+
required: true
|
|
14
|
+
outputs:
|
|
15
|
+
valid:
|
|
16
|
+
description: Whether the agent output is a valid review outcome.
|
|
17
|
+
value: ${{ steps.validate.outputs.valid }}
|
|
18
|
+
outcome:
|
|
19
|
+
description: "Deterministic review outcome: implemented, already-satisfied, needs-human, or invalid."
|
|
20
|
+
value: ${{ steps.validate.outputs.outcome }}
|
|
21
|
+
runs:
|
|
22
|
+
using: composite
|
|
23
|
+
steps:
|
|
24
|
+
- name: Validate review outcome
|
|
25
|
+
id: validate
|
|
26
|
+
shell: bash
|
|
27
|
+
env:
|
|
28
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
29
|
+
PR_NUMBER: ${{ inputs.pr-number }}
|
|
30
|
+
REVIEW_THREADS_FILE: ${{ inputs.review-threads-file }}
|
|
31
|
+
run: |
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
outcome="$(bash "${{ github.action_path }}/validate-review-output.sh" "$OUTPUT_FILE" "$PR_NUMBER" "$REVIEW_THREADS_FILE")"
|
|
34
|
+
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
35
|
+
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-review-output/validate-review-output.sh. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
|
+
# Print implemented, already-satisfied, needs-human, or invalid.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
output_file="$1"
|
|
8
|
+
pr_number="$2"
|
|
9
|
+
threads_file="$3"
|
|
10
|
+
|
|
11
|
+
if [ ! -f "$output_file" ] || [ ! -f "$threads_file" ] \
|
|
12
|
+
|| ! jq -e '.items | arrays' "$output_file" >/dev/null 2>&1 \
|
|
13
|
+
|| ! jq -e 'type == "array"' "$threads_file" >/dev/null 2>&1; then
|
|
14
|
+
echo invalid
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
jq -r --arg pr "$pr_number" --slurpfile threads "$threads_file" '
|
|
19
|
+
.items as $items
|
|
20
|
+
| ($threads[0] | map(select(.isResolved == false and .isOutdated == false) | .id) | sort) as $expected
|
|
21
|
+
| ($items | [.[] | select(.type == "add_comment" and (.item_number | tostring) == $pr and (.body | type == "string"))]) as $comments
|
|
22
|
+
| ($comments | map(.body | if test("\\*\\*Review outcome:\\*\\*\\s*(implemented|already-satisfied|needs-human)"; "i") then capture("\\*\\*Review outcome:\\*\\*\\s*(?<v>implemented|already-satisfied|needs-human)"; "i").v | ascii_downcase else empty end) | .[0] // "invalid") as $outcome
|
|
23
|
+
| ($comments | map(.body | scan("PRRT_[A-Za-z0-9_=-]+")) | add | unique | sort) as $reported
|
|
24
|
+
| ([$items[] | select(.type == "push_to_pull_request_branch")] | length) as $pushes
|
|
25
|
+
| if $expected != $reported then "invalid"
|
|
26
|
+
elif $outcome == "implemented" and $pushes == 1 then "implemented"
|
|
27
|
+
elif ($outcome == "already-satisfied" or $outcome == "needs-human") and $pushes == 0 then $outcome
|
|
28
|
+
else "invalid"
|
|
29
|
+
end
|
|
30
|
+
' "$output_file"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-triage-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Validate triage output
|
|
3
|
+
description: Verify agent_output.json contains a usable triage verdict before any GitHub writes.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
issue-number:
|
|
9
|
+
description: Issue number that every triage outcome must target.
|
|
10
|
+
required: true
|
|
11
|
+
outputs:
|
|
12
|
+
valid:
|
|
13
|
+
description: Whether the output contains a usable triage comment with a verdict.
|
|
14
|
+
value: ${{ steps.validate.outputs.valid }}
|
|
15
|
+
outcome:
|
|
16
|
+
description: "Deterministic triage outcome: pass, needs-info, block, or invalid."
|
|
17
|
+
value: ${{ steps.validate.outputs.outcome }}
|
|
18
|
+
runs:
|
|
19
|
+
using: composite
|
|
20
|
+
steps:
|
|
21
|
+
- name: Validate triage outcome
|
|
22
|
+
id: validate
|
|
23
|
+
shell: bash
|
|
24
|
+
env:
|
|
25
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
26
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
27
|
+
run: |
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
outcome="$(bash "${{ github.action_path }}/validate-triage-output.sh" "$OUTPUT_FILE" "$ISSUE_NUMBER")"
|
|
31
|
+
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
32
|
+
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
33
|
+
|
|
34
|
+
if [ "$outcome" = 'invalid' ]; then
|
|
35
|
+
echo "::warning::Agent output has no usable triage outcome. It will not be applied."
|
|
36
|
+
fi
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-triage-output/validate-triage-output.sh. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
|
+
# Print the deterministic triage outcome: pass, needs-info, block, or invalid.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
output_file="$1"
|
|
8
|
+
issue_number="$2"
|
|
9
|
+
|
|
10
|
+
if [ ! -f "$output_file" ] || ! jq -e '.items | arrays' "$output_file" >/dev/null 2>&1; then
|
|
11
|
+
echo invalid
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# The agent emits exactly one add_comment on the source issue. The comment body
|
|
16
|
+
# must contain a verdict line matching **Verdict:** pass|needs-info|block.
|
|
17
|
+
# capture() returns an object — use a named group (?<v>...) to extract the value.
|
|
18
|
+
# test() before capture() avoids jq errors on non-matching bodies.
|
|
19
|
+
jq -r --arg issue "$issue_number" '
|
|
20
|
+
def extract_verdict:
|
|
21
|
+
[.items[] | select(.type == "add_comment" and (.item_number | tostring) == $issue and (.body | type == "string"))]
|
|
22
|
+
| map(.body |
|
|
23
|
+
if test("\\*\\*Verdict:\\*\\*\\s*(pass|needs-info|block)"; "i") then
|
|
24
|
+
capture("\\*\\*Verdict:\\*\\*\\s*(?<v>pass|needs-info|block)"; "i").v | ascii_downcase
|
|
25
|
+
else empty end
|
|
26
|
+
)
|
|
27
|
+
| .[0] // "none";
|
|
28
|
+
|
|
29
|
+
extract_verdict as $verdict |
|
|
30
|
+
if $verdict == "pass" or $verdict == "needs-info" or $verdict == "block" then
|
|
31
|
+
$verdict
|
|
32
|
+
else
|
|
33
|
+
"invalid"
|
|
34
|
+
end
|
|
35
|
+
' "$output_file"
|
|
36
|
+
|
|
@@ -65,8 +65,12 @@ assert_route "implement label routes to implement" implement \
|
|
|
65
65
|
EVENT=issues ACTION=labeled LABEL=implement EVENT_ISSUE_NUMBER=42
|
|
66
66
|
assert_route "unrelated label routes nowhere" none \
|
|
67
67
|
EVENT=issues ACTION=labeled LABEL=documentation EVENT_ISSUE_NUMBER=42
|
|
68
|
-
assert_route "a non-label issue action routes
|
|
68
|
+
assert_route "a non-label issue action routes to triage" triage \
|
|
69
69
|
EVENT=issues ACTION=opened EVENT_ISSUE_NUMBER=42
|
|
70
|
+
assert_route "a human triage label routes to triage" triage \
|
|
71
|
+
EVENT=issues ACTION=labeled LABEL=triage ACTOR=maintainer EVENT_ISSUE_NUMBER=42
|
|
72
|
+
assert_route "a bot triage label routes nowhere" none \
|
|
73
|
+
EVENT=issues ACTION=labeled LABEL=triage ACTOR=platform-devbox[bot] EVENT_ISSUE_NUMBER=42
|
|
70
74
|
assert "refine label starts a first pass" first \
|
|
71
75
|
"$(route_field refine-mode EVENT=issues ACTION=labeled LABEL=refine EVENT_ISSUE_NUMBER=42)"
|
|
72
76
|
assert_route "direct label routes to direct" direct \
|
|
@@ -98,6 +102,15 @@ assert "a direct reply is a continue pass" continue \
|
|
|
98
102
|
assert_route "the bot's own comment never re-enters direct" none \
|
|
99
103
|
EVENT=issue_comment COMMENT_ON_PR=false COMMENT_SENDER_TYPE=Bot \
|
|
100
104
|
'ISSUE_LABELS=["direct"]' EVENT_ISSUE_NUMBER=42
|
|
105
|
+
assert_route "a comment on a triage issue re-triages" triage \
|
|
106
|
+
EVENT=issue_comment COMMENT_ON_PR=false COMMENT_SENDER_TYPE=User \
|
|
107
|
+
'ISSUE_LABELS=["triage"]' EVENT_ISSUE_NUMBER=42
|
|
108
|
+
assert "a triage re-trigger is a retriage pass" retriage \
|
|
109
|
+
"$(route_field triage-mode EVENT=issue_comment COMMENT_ON_PR=false \
|
|
110
|
+
COMMENT_SENDER_TYPE=User 'ISSUE_LABELS=["triage"]' EVENT_ISSUE_NUMBER=42)"
|
|
111
|
+
assert_route "the bot's own comment never re-enters triage" none \
|
|
112
|
+
EVENT=issue_comment COMMENT_ON_PR=false COMMENT_SENDER_TYPE=Bot \
|
|
113
|
+
'ISSUE_LABELS=["triage"]' EVENT_ISSUE_NUMBER=42
|
|
101
114
|
|
|
102
115
|
echo "── Review events ─────────────────────────────────────────────────────────"
|
|
103
116
|
assert_route "a review comment routes to apply-review" apply-review \
|
|
@@ -142,6 +155,12 @@ assert_route "direct dispatch accepts a positive issue" direct \
|
|
|
142
155
|
EVENT=workflow_dispatch OPERATION=direct INPUT_ISSUE_NUMBER=42
|
|
143
156
|
assert_route "direct dispatch needs an issue number" none \
|
|
144
157
|
EVENT=workflow_dispatch OPERATION=direct INPUT_ISSUE_NUMBER=
|
|
158
|
+
assert_route "triage dispatch accepts a positive issue" triage \
|
|
159
|
+
EVENT=workflow_dispatch OPERATION=triage INPUT_ISSUE_NUMBER=42
|
|
160
|
+
assert_route "triage dispatch needs an issue number" none \
|
|
161
|
+
EVENT=workflow_dispatch OPERATION=triage INPUT_ISSUE_NUMBER=
|
|
162
|
+
assert "triage dispatch defaults to first pass" first \
|
|
163
|
+
"$(route_field triage-mode EVENT=workflow_dispatch OPERATION=triage INPUT_ISSUE_NUMBER=42)"
|
|
145
164
|
assert_route "merge-gate dispatch needs a pull request number" none \
|
|
146
165
|
EVENT=workflow_dispatch OPERATION=merge-gate INPUT_PR_NUMBER=0
|
|
147
166
|
assert_route "merge-gate dispatch accepts a positive pull request" merge-gate \
|
|
@@ -185,7 +204,18 @@ for route in refine implement direct apply-review; do
|
|
|
185
204
|
fi
|
|
186
205
|
done
|
|
187
206
|
|
|
188
|
-
|
|
207
|
+
# Triage runs under a trusted App identity. Outside collaborators are admitted only to
|
|
208
|
+
# the deterministic dispatcher; the worker call itself requires a trusted actor.
|
|
209
|
+
if grep -qE "dispatch-triage:.*" "$ROUTER_YML" && \
|
|
210
|
+
grep -qE "route == 'triage'.*is_outside_collaborator == 'true'" "$ROUTER_YML" && \
|
|
211
|
+
grep -qE "route == 'triage'.*trusted == 'true'" "$ROUTER_YML"; then
|
|
212
|
+
PASS=$((PASS + 1))
|
|
213
|
+
else
|
|
214
|
+
FAIL=$((FAIL + 1))
|
|
215
|
+
echo "FAIL: route 'triage' does not dispatch outside collaborators and require a trusted worker actor" >&2
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
for route in refine implement direct triage apply-review merge-gate audit propose bot-approve \
|
|
189
219
|
audit-close cleanup-artifacts reconcile-bot-pr-runs stale-recovery validate; do
|
|
190
220
|
if grep -q "route == '${route}'" "$ROUTER_YML"; then
|
|
191
221
|
PASS=$((PASS + 1))
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Report a defect
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thanks for reporting. Do **not** include real secrets or production data — use placeholders.
|
|
9
|
+
|
|
10
|
+
For security vulnerabilities, contact the team directly instead of filing a public issue.
|
|
11
|
+
- type: dropdown
|
|
12
|
+
id: severity
|
|
13
|
+
attributes:
|
|
14
|
+
label: Severity
|
|
15
|
+
options:
|
|
16
|
+
- Production-down / data loss
|
|
17
|
+
- Major — broken core flow
|
|
18
|
+
- Minor — broken edge case
|
|
19
|
+
- Cosmetic / polish
|
|
20
|
+
validations:
|
|
21
|
+
required: true
|
|
22
|
+
- type: dropdown
|
|
23
|
+
id: frequency
|
|
24
|
+
attributes:
|
|
25
|
+
label: Frequency
|
|
26
|
+
description: How often does this happen?
|
|
27
|
+
options:
|
|
28
|
+
- Always
|
|
29
|
+
- Intermittent
|
|
30
|
+
- Only once
|
|
31
|
+
validations:
|
|
32
|
+
required: true
|
|
33
|
+
- type: textarea
|
|
34
|
+
id: what-happened
|
|
35
|
+
attributes:
|
|
36
|
+
label: What happened?
|
|
37
|
+
description: A clear description of the bug and its impact.
|
|
38
|
+
validations:
|
|
39
|
+
required: true
|
|
40
|
+
- type: textarea
|
|
41
|
+
id: repro
|
|
42
|
+
attributes:
|
|
43
|
+
label: Steps to reproduce
|
|
44
|
+
description: How can a maintainer reproduce it?
|
|
45
|
+
placeholder: |
|
|
46
|
+
1. Go to ...
|
|
47
|
+
2. Do ...
|
|
48
|
+
3. See error
|
|
49
|
+
validations:
|
|
50
|
+
required: true
|
|
51
|
+
- type: textarea
|
|
52
|
+
id: expected
|
|
53
|
+
attributes:
|
|
54
|
+
label: Expected behavior
|
|
55
|
+
validations:
|
|
56
|
+
required: true
|
|
57
|
+
- type: dropdown
|
|
58
|
+
id: area
|
|
59
|
+
attributes:
|
|
60
|
+
label: Where does it occur?
|
|
61
|
+
options:
|
|
62
|
+
- Backend
|
|
63
|
+
- Frontend
|
|
64
|
+
- Both
|
|
65
|
+
- CLI / tooling
|
|
66
|
+
- Configuration / deployment
|
|
67
|
+
- Documentation
|
|
68
|
+
- Other
|
|
69
|
+
validations:
|
|
70
|
+
required: true
|
|
71
|
+
- type: textarea
|
|
72
|
+
id: acceptance
|
|
73
|
+
attributes:
|
|
74
|
+
label: Acceptance criteria
|
|
75
|
+
description: How will we know it's fixed? A checklist a reviewer can verify (include the test that should turn green).
|
|
76
|
+
placeholder: |
|
|
77
|
+
- [ ] ...
|
|
78
|
+
- [ ] A test that fails before the fix and passes after
|
|
79
|
+
validations:
|
|
80
|
+
required: false
|
|
81
|
+
- type: textarea
|
|
82
|
+
id: open-questions
|
|
83
|
+
attributes:
|
|
84
|
+
label: Open questions
|
|
85
|
+
description: Decisions a maintainer must make before this can be planned. Leave blank if none.
|
|
86
|
+
validations:
|
|
87
|
+
required: false
|
|
88
|
+
- type: textarea
|
|
89
|
+
id: environment
|
|
90
|
+
attributes:
|
|
91
|
+
label: Environment
|
|
92
|
+
description: Local vs deployed, branch/commit, OS, runtime versions if relevant.
|
|
93
|
+
validations:
|
|
94
|
+
required: false
|
|
95
|
+
- type: textarea
|
|
96
|
+
id: logs
|
|
97
|
+
attributes:
|
|
98
|
+
label: Logs / screenshots
|
|
99
|
+
description: Paste relevant logs or screenshots. Redact secrets and PII.
|
|
100
|
+
render: shell
|
|
101
|
+
validations:
|
|
102
|
+
required: false
|
|
103
|
+
- type: textarea
|
|
104
|
+
id: related
|
|
105
|
+
attributes:
|
|
106
|
+
label: Related links
|
|
107
|
+
description: Links to related issues, PRs, discussions, or docs. Leave blank if none.
|
|
108
|
+
validations:
|
|
109
|
+
required: false
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Propose a small improvement or new capability
|
|
3
|
+
labels: ["enhancement"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
This template is for small, well-scoped improvements — a focused
|
|
9
|
+
enhancement, a missing convenience, a quality-of-life fix.
|
|
10
|
+
|
|
11
|
+
For large features, architecture changes, or cross-cutting work,
|
|
12
|
+
open a planning issue or an ADR instead.
|
|
13
|
+
|
|
14
|
+
Do **not** include real secrets or production data — use placeholders.
|
|
15
|
+
- type: dropdown
|
|
16
|
+
id: scope
|
|
17
|
+
attributes:
|
|
18
|
+
label: Scope
|
|
19
|
+
description: How big is this change? If it doesn't fit Small or Medium, use a planning issue instead.
|
|
20
|
+
options:
|
|
21
|
+
- Small — a single file or component, no refactoring
|
|
22
|
+
- Medium — touches a few files or a feature area, may need minor refactoring
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: current-behavior
|
|
27
|
+
attributes:
|
|
28
|
+
label: Current behavior
|
|
29
|
+
description: How does it work today? Describe the existing behavior or limitation.
|
|
30
|
+
validations:
|
|
31
|
+
required: true
|
|
32
|
+
- type: textarea
|
|
33
|
+
id: problem
|
|
34
|
+
attributes:
|
|
35
|
+
label: Problem / motivation
|
|
36
|
+
description: What user or business need does this address? Why is the current behavior a problem?
|
|
37
|
+
validations:
|
|
38
|
+
required: true
|
|
39
|
+
- type: textarea
|
|
40
|
+
id: proposal
|
|
41
|
+
attributes:
|
|
42
|
+
label: Proposed solution
|
|
43
|
+
description: What would you like to happen?
|
|
44
|
+
validations:
|
|
45
|
+
required: true
|
|
46
|
+
- type: textarea
|
|
47
|
+
id: alternatives
|
|
48
|
+
attributes:
|
|
49
|
+
label: Alternatives considered
|
|
50
|
+
validations:
|
|
51
|
+
required: false
|
|
52
|
+
- type: textarea
|
|
53
|
+
id: acceptance
|
|
54
|
+
attributes:
|
|
55
|
+
label: Acceptance criteria
|
|
56
|
+
description: A checklist a reviewer can verify when this is done (include the tests).
|
|
57
|
+
placeholder: |
|
|
58
|
+
- [ ] ...
|
|
59
|
+
- [ ] Tests
|
|
60
|
+
validations:
|
|
61
|
+
required: false
|
|
62
|
+
- type: textarea
|
|
63
|
+
id: open-questions
|
|
64
|
+
attributes:
|
|
65
|
+
label: Open questions
|
|
66
|
+
description: Decisions a maintainer must make before this can be planned. Leave blank if none.
|
|
67
|
+
validations:
|
|
68
|
+
required: false
|
|
69
|
+
- type: textarea
|
|
70
|
+
id: related
|
|
71
|
+
attributes:
|
|
72
|
+
label: Related links
|
|
73
|
+
description: Links to related issues, PRs, discussions, or docs. Leave blank if none.
|
|
74
|
+
validations:
|
|
75
|
+
required: false
|
|
@@ -140,11 +140,52 @@ jobs:
|
|
|
140
140
|
token: ${{ steps.app-token.outputs.token }}
|
|
141
141
|
issue-number: ${{ needs.subject.outputs.issue }}
|
|
142
142
|
labels: ${{ env.REVIEW_LABEL }}
|
|
143
|
-
|
|
143
|
+
validate_output:
|
|
144
144
|
needs: [activation, subject, agent, safe_outputs]
|
|
145
|
+
if: always() && needs.agent.result == 'success' && needs.safe_outputs.result == 'success'
|
|
146
|
+
runs-on: RunnerLandingZone
|
|
147
|
+
permissions:
|
|
148
|
+
contents: read
|
|
149
|
+
pull-requests: read
|
|
150
|
+
outputs:
|
|
151
|
+
valid: ${{ steps.validate.outputs.valid }}
|
|
152
|
+
outcome: ${{ steps.validate.outputs.outcome }}
|
|
153
|
+
steps:
|
|
154
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
155
|
+
with:
|
|
156
|
+
persist-credentials: false
|
|
157
|
+
- id: output
|
|
158
|
+
uses: ./.github/actions/download-agent-output
|
|
159
|
+
with:
|
|
160
|
+
artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
|
|
161
|
+
- name: Load unresolved review threads
|
|
162
|
+
env:
|
|
163
|
+
GH_TOKEN: ${{ github.token }}
|
|
164
|
+
REPO: ${{ github.repository }}
|
|
165
|
+
PR: ${{ needs.subject.outputs.pr }}
|
|
166
|
+
run: |
|
|
167
|
+
set -euo pipefail
|
|
168
|
+
gh api graphql -f query='
|
|
169
|
+
query($owner:String!, $name:String!, $number:Int!) {
|
|
170
|
+
repository(owner:$owner, name:$name) {
|
|
171
|
+
pullRequest(number:$number) {
|
|
172
|
+
reviewThreads(first:100) { nodes { id isResolved isOutdated } }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}' -f owner="${REPO%/*}" -f name="${REPO#*/}" -F number="$PR" \
|
|
176
|
+
--jq '.data.repository.pullRequest.reviewThreads.nodes' > /tmp/review-threads.json
|
|
177
|
+
- id: validate
|
|
178
|
+
uses: ./.github/actions/validate-review-output
|
|
179
|
+
with:
|
|
180
|
+
output-file: ${{ steps.output.outputs.output-file }}
|
|
181
|
+
pr-number: ${{ needs.subject.outputs.pr }}
|
|
182
|
+
review-threads-file: /tmp/review-threads.json
|
|
183
|
+
conclude:
|
|
184
|
+
needs: [activation, subject, agent, safe_outputs, validate_output]
|
|
145
185
|
if: >
|
|
146
186
|
needs.agent.result == 'success' &&
|
|
147
|
-
needs.safe_outputs.result == 'success'
|
|
187
|
+
needs.safe_outputs.result == 'success' &&
|
|
188
|
+
needs.validate_output.outputs.valid == 'true'
|
|
148
189
|
runs-on: RunnerLandingZone
|
|
149
190
|
permissions:
|
|
150
191
|
contents: write
|
|
@@ -168,12 +209,34 @@ jobs:
|
|
|
168
209
|
artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
|
|
169
210
|
token: ${{ steps.app-token.outputs.token }}
|
|
170
211
|
push-to-branch: 'true'
|
|
212
|
+
apply-labels: 'false'
|
|
213
|
+
- name: Release implemented review
|
|
214
|
+
if: needs.validate_output.outputs.outcome == 'implemented'
|
|
215
|
+
uses: ./.github/actions/remove-issue-labels
|
|
216
|
+
with:
|
|
217
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
218
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
219
|
+
labels: ${{ env.WORKING_LABEL }}
|
|
220
|
+
- name: Flag review outcome
|
|
221
|
+
if: needs.validate_output.outputs.outcome != 'implemented'
|
|
222
|
+
uses: ./.github/actions/add-issue-labels
|
|
223
|
+
with:
|
|
224
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
225
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
226
|
+
labels: ${{ env.REVIEW_LABEL }}
|
|
227
|
+
- name: Release review outcome
|
|
228
|
+
if: needs.validate_output.outputs.outcome != 'implemented'
|
|
229
|
+
uses: ./.github/actions/remove-issue-labels
|
|
230
|
+
with:
|
|
231
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
232
|
+
issue-number: ${{ needs.subject.outputs.issue }}
|
|
233
|
+
labels: ${{ env.WORKING_LABEL }}
|
|
171
234
|
incomplete:
|
|
172
|
-
needs: [subject, agent, safe_outputs]
|
|
235
|
+
needs: [subject, agent, safe_outputs, validate_output]
|
|
173
236
|
if: >
|
|
174
237
|
always() &&
|
|
175
238
|
needs.subject.outputs.found == 'true' &&
|
|
176
|
-
needs.agent.result != 'success'
|
|
239
|
+
(needs.agent.result != 'success' || needs.safe_outputs.result != 'success' || needs.validate_output.outputs.valid != 'true')
|
|
177
240
|
runs-on: RunnerLandingZone
|
|
178
241
|
permissions:
|
|
179
242
|
contents: read
|
|
@@ -262,8 +325,8 @@ steps:
|
|
|
262
325
|
query($owner:String!, $name:String!, $number:Int!) {
|
|
263
326
|
repository(owner:$owner, name:$name) {
|
|
264
327
|
pullRequest(number:$number) {
|
|
265
|
-
|
|
266
|
-
|
|
328
|
+
reviewThreads(first:100) {
|
|
329
|
+
nodes { id
|
|
267
330
|
isResolved isOutdated path
|
|
268
331
|
comments(first:50) { nodes { author { login } body } }
|
|
269
332
|
}
|
|
@@ -280,8 +343,7 @@ safe-outputs:
|
|
|
280
343
|
threat-detection: false
|
|
281
344
|
push-to-pull-request-branch:
|
|
282
345
|
add-comment:
|
|
283
|
-
|
|
284
|
-
remove-labels:
|
|
346
|
+
target: "*"
|
|
285
347
|
|
|
286
348
|
|
|
287
349
|
timeout-minutes: 45
|
|
@@ -310,8 +372,9 @@ timeout-minutes: 45
|
|
|
310
372
|
|
|
311
373
|
4. Work out which items are genuinely actionable and still outstanding. Skip anything already
|
|
312
374
|
addressed by a later commit, anything a bot wrote, anything from the pull request author,
|
|
313
|
-
and anything that is a question rather than a request.
|
|
314
|
-
|
|
375
|
+
and anything that is a question rather than a request. You must account for every unresolved
|
|
376
|
+
human review thread by its `PRRT_...` ID. Do not claim feedback was implemented when no branch
|
|
377
|
+
change is needed: that requires reviewer confirmation.
|
|
315
378
|
|
|
316
379
|
5. Load only skills required by the feedback, then apply it. Make only changes the feedback
|
|
317
380
|
justifies: a review comment is not licence for unrelated refactoring. Never read outside this
|
|
@@ -327,17 +390,24 @@ timeout-minutes: 45
|
|
|
327
390
|
${{ env.VERIFY_COMMANDS }}
|
|
328
391
|
```
|
|
329
392
|
|
|
330
|
-
7.
|
|
331
|
-
|
|
393
|
+
7. Select one review outcome.
|
|
394
|
+
|
|
395
|
+
- **implemented**: You made the requested change, verification passed, and you will propose
|
|
396
|
+
exactly one `push_to_pull_request_branch`.
|
|
397
|
+
- **already-satisfied**: The current branch already satisfies the request. Do not push; the
|
|
398
|
+
reviewer must confirm this assessment.
|
|
399
|
+
- **needs-human**: The feedback is ambiguous, unsafe, or cannot be applied. Do not push.
|
|
400
|
+
|
|
401
|
+
8. Emit exactly one `add_comment` on PR `${{ needs.subject.outputs.pr }}`. Include every
|
|
402
|
+
unresolved human review thread ID and an explanation for it, then exactly one line:
|
|
403
|
+
`**Review outcome:** implemented`, `**Review outcome:** already-satisfied`, or
|
|
404
|
+
`**Review outcome:** needs-human`.
|
|
332
405
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
followed by a short list of what you changed and, if anything was deliberately not changed,
|
|
336
|
-
which item and why.
|
|
406
|
+
9. Do not merge, close, or change labels. The workflow validates your outcome and owns those
|
|
407
|
+
state transitions.
|
|
337
408
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
looking untouched.
|
|
409
|
+
10. Ignore the `## Diagram` section below. It is documentation for humans and contains no
|
|
410
|
+
instructions for you.
|
|
341
411
|
|
|
342
412
|
## Diagram
|
|
343
413
|
|
|
@@ -120,7 +120,7 @@ safe-outputs:
|
|
|
120
120
|
timeout-minutes: 45
|
|
121
121
|
---
|
|
122
122
|
|
|
123
|
-
1. Call skill("
|
|
123
|
+
1. Call skill("pc-repo-audit"), then run `/repo-audit` as a read-only audit of this
|
|
124
124
|
repository. Do not modify any file, do not commit, and do not push.
|
|
125
125
|
|
|
126
126
|
2. Apply repository documentation and established conventions while auditing. Focus on
|
|
@@ -156,7 +156,7 @@ timeout-minutes: 45
|
|
|
156
156
|
**Section 1 , All findings:** A numbered list of every finding (5-7) with its score,
|
|
157
157
|
file path, and a one-line description. Order by score descending.
|
|
158
158
|
|
|
159
|
-
**Section 2 , Top 3 to implement:** Call skill("
|
|
159
|
+
**Section 2 , Top 3 to implement:** Call skill("pc-plan-story") and refine the top 3
|
|
160
160
|
findings by score into user stories in Mike Cohn's As a / I want to / so that format
|
|
161
161
|
with Given/When/Then acceptance criteria, edge cases, and likely files to change. Mark
|
|
162
162
|
this section clearly with a heading like `## Top 3 , To Implement`.
|
|
@@ -134,6 +134,18 @@ jobs:
|
|
|
134
134
|
token: ${{ steps.app-token.outputs.token }}
|
|
135
135
|
pr-number: ${{ needs.safe_outputs.outputs.created_pr_number }}
|
|
136
136
|
issue-number: ${{ inputs.issue-number }}
|
|
137
|
+
- name: Reconcile the new bot pull request
|
|
138
|
+
if: needs.safe_outputs.outputs.created_pr_number != ''
|
|
139
|
+
env:
|
|
140
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
141
|
+
REPO: ${{ github.repository }}
|
|
142
|
+
REF: ${{ github.event.repository.default_branch }}
|
|
143
|
+
run: |
|
|
144
|
+
set -euo pipefail
|
|
145
|
+
# GitHub may create the pending CI run shortly after the PR appears.
|
|
146
|
+
sleep 60
|
|
147
|
+
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
|
|
148
|
+
-f operation=reconcile-bot-pr-runs
|
|
137
149
|
incomplete:
|
|
138
150
|
needs: [agent, safe_outputs, eligibility]
|
|
139
151
|
if: >
|
|
@@ -246,8 +258,8 @@ timeout-minutes: 180
|
|
|
246
258
|
left off and act on the latest comment.
|
|
247
259
|
|
|
248
260
|
3. The issue body or the latest human comment is a **free-form instruction**. Execute it
|
|
249
|
-
exactly as asked. Load whatever skills the instruction names (`
|
|
250
|
-
`
|
|
261
|
+
exactly as asked. Load whatever skills the instruction names (`pc-plan-explore`,
|
|
262
|
+
`pc-plan-propose`, `pc-plan-apply`, `pc-make-engineer`, etc.). The instruction may ask you
|
|
251
263
|
to explore, plan, implement, create files, refactor, or anything else a maintainer would do
|
|
252
264
|
manually.
|
|
253
265
|
|