@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.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/dist/action-validation.test.d.ts +1 -0
  3. package/dist/action-validation.test.js +84 -0
  4. package/dist/catalog-installation.js +30 -2
  5. package/dist/catalog-installation.test.js +17 -0
  6. package/dist/catalog-listing.js +6 -4
  7. package/dist/catalog-listing.test.js +15 -2
  8. package/dist/index.js +2 -1
  9. package/dist/index.test.js +7 -7
  10. package/dist/route-processing.test.js +12 -4
  11. package/dist/tui.test.js +8 -8
  12. package/dist/workflow-catalog.d.ts +3 -2
  13. package/dist/workflow-catalog.js +6 -0
  14. package/dist/workflow-catalog.test.js +1 -1
  15. package/loops/actions/classify-route/action.yml +3 -0
  16. package/loops/actions/classify-route/classify-route.sh +38 -3
  17. package/loops/actions/load-issue-context/action.yml +2 -0
  18. package/loops/actions/validate-merge-gate-output/action.yml +40 -0
  19. package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +34 -0
  20. package/loops/actions/validate-review-output/action.yml +35 -0
  21. package/loops/actions/validate-review-output/validate-review-output.sh +30 -0
  22. package/loops/actions/validate-triage-output/action.yml +36 -0
  23. package/loops/actions/validate-triage-output/validate-triage-output.sh +36 -0
  24. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +32 -2
  25. package/loops/templates/issues/bug_report.yml +109 -0
  26. package/loops/templates/issues/feature_request.yml +75 -0
  27. package/loops/workflows/agent-apply-review.md +89 -19
  28. package/loops/workflows/agent-audit.md +2 -2
  29. package/loops/workflows/agent-direct.md +14 -2
  30. package/loops/workflows/agent-implement.md +56 -16
  31. package/loops/workflows/agent-merge-gate.md +159 -60
  32. package/loops/workflows/agent-propose.md +1 -1
  33. package/loops/workflows/agent-refine.md +45 -6
  34. package/loops/workflows/agent-triage.md +439 -0
  35. package/loops/workflows/authorize-bot-work.yml +2 -1
  36. package/loops/workflows/work-router.yml +89 -8
  37. 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 nowhere" none \
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
- for route in refine implement direct apply-review merge-gate audit propose bot-approve \
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
- conclude:
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
- reviewThreads(first:100) {
266
- nodes {
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
- add-labels:
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. If nothing is actionable, call
314
- `add_labels` to add `${{ env.REVIEW_LABEL }}`, then call `add_comment` saying so and stop.
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. Call `push_to_pull_request_branch` to push the verified changes. Do not merge, do not
331
- close anything, and do not change any label other than `bot-working`.
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
- 8. Call `add_comment` once:
334
- `Feedback implemented. Please review and merge when ready.`
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
- 9. Call `remove_labels` to remove `bot-working`. If you failed, call `add_labels` to add
339
- `${{ env.REVIEW_LABEL }}`, then say so in the comment rather than leaving the pull request
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("ob-repo-audit"), then run `/repo-audit` as a read-only audit of this
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("ob-plan-story") and refine the top 3
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 (`ob-plan-explore`,
250
- `ob-plan-propose`, `ob-plan-apply`, `ob-make-engineer`, etc.). The instruction may ask you
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