@plainconceptsplatform/workflows 0.4.15 → 0.4.20

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.
@@ -48,6 +48,7 @@ runs:
48
48
  EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
49
49
  COMMENT_ON_PR: ${{ github.event.issue.pull_request != '' }}
50
50
  COMMENT_SENDER_TYPE: ${{ github.event.comment.user.type }}
51
+ ACTOR: ${{ github.actor }}
51
52
  RUN_PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
52
53
  RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
53
54
  RUN_ID: ${{ github.event.workflow_run.id }}
@@ -53,9 +53,19 @@ classify_route() {
53
53
  fi
54
54
  ;;
55
55
  refine | implement | direct)
56
- # Human adds work label authorize-bot-work.yml handles this
57
- # Route to none here; the bot will add bot-working which triggers the actual work
58
- error="waiting for bot to add bot-working label"
56
+ # If the actor is a bot (e.g. refine→implement transition), route directly.
57
+ # If the actor is a human, authorize-bot-work.yml will add bot-working which triggers the workflow.
58
+ if [ "${ACTOR:-}" != "" ] && echo "${ACTOR:-}" | grep -q '\[bot\]$'; then
59
+ route="${LABEL:-}"
60
+ issue_number="${EVENT_ISSUE_NUMBER:-}"
61
+ if [ "${LABEL:-}" = "refine" ]; then
62
+ refine_mode="first"
63
+ elif [ "${LABEL:-}" = "direct" ]; then
64
+ direct_mode="first"
65
+ fi
66
+ else
67
+ error="waiting for bot to add bot-working label"
68
+ fi
59
69
  ;;
60
70
  esac
61
71
  fi
@@ -67,6 +77,8 @@ classify_route() {
67
77
  pr_number="${EVENT_ISSUE_NUMBER:-}"
68
78
  elif [ "${COMMENT_SENDER_TYPE:-}" = "Bot" ]; then
69
79
  error="comment authored by a bot"
80
+ elif has_label implement; then
81
+ error="issue has implement label; comments do not re-trigger implement"
70
82
  elif has_label direct; then
71
83
  route="direct"
72
84
  direct_mode="continue"
@@ -26,7 +26,7 @@ concurrency:
26
26
  jobs:
27
27
  verify-lockfiles:
28
28
  name: Verify generated agent lockfiles
29
- runs-on: ubuntu-latest
29
+ runs-on: RunnerLandingZone
30
30
  timeout-minutes: 10
31
31
  steps:
32
32
  - name: Checkout repository
@@ -56,7 +56,7 @@ jobs:
56
56
 
57
57
  lint-workflows:
58
58
  name: Lint authored workflows and shell
59
- runs-on: ubuntu-latest
59
+ runs-on: RunnerLandingZone
60
60
  timeout-minutes: 10
61
61
  steps:
62
62
  - name: Checkout repository
@@ -48,7 +48,7 @@ permissions: {}
48
48
  jobs:
49
49
  close-expired-discussions:
50
50
  if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
51
- runs-on: ubuntu-latest
51
+ runs-on: RunnerLandingZone
52
52
  permissions:
53
53
  discussions: write
54
54
  steps:
@@ -66,7 +66,7 @@ jobs:
66
66
  await main();
67
67
  close-expired-issues:
68
68
  if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
69
- runs-on: ubuntu-latest
69
+ runs-on: RunnerLandingZone
70
70
  permissions:
71
71
  issues: write
72
72
  steps:
@@ -84,7 +84,7 @@ jobs:
84
84
  await main();
85
85
  close-expired-pull-requests:
86
86
  if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
87
- runs-on: ubuntu-latest
87
+ runs-on: RunnerLandingZone
88
88
  permissions:
89
89
  pull-requests: write
90
90
  steps:
@@ -102,7 +102,7 @@ jobs:
102
102
  await main();
103
103
  cleanup-cache-memory:
104
104
  if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }}
105
- runs-on: ubuntu-latest
105
+ runs-on: RunnerLandingZone
106
106
  permissions:
107
107
  actions: write
108
108
  steps:
@@ -47,7 +47,7 @@ jobs:
47
47
  api:
48
48
  name: API (.NET)
49
49
  if: github.event_name != 'schedule'
50
- runs-on: ubuntu-latest
50
+ runs-on: RunnerLandingZone
51
51
  timeout-minutes: 30
52
52
  services:
53
53
  sqlserver:
@@ -104,7 +104,7 @@ jobs:
104
104
  web:
105
105
  name: Web (Next.js)
106
106
  if: github.event_name != 'schedule'
107
- runs-on: ubuntu-latest
107
+ runs-on: RunnerLandingZone
108
108
  timeout-minutes: 15
109
109
  steps:
110
110
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -134,14 +134,14 @@ jobs:
134
134
 
135
135
  secret-scan:
136
136
  name: Secret scan (TruffleHog)
137
- runs-on: ubuntu-latest
137
+ runs-on: RunnerLandingZone
138
138
  steps:
139
139
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
140
140
  - run: docker run --rm -v "${{ github.workspace }}:/repo" "$TRUFFLEHOG_IMAGE" filesystem /repo --only-verified --fail --no-update
141
141
 
142
142
  deps-and-iac-scan:
143
143
  name: Dependencies and IaC (Trivy)
144
- runs-on: ubuntu-latest
144
+ runs-on: RunnerLandingZone
145
145
  steps:
146
146
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
147
147
  - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
@@ -155,14 +155,14 @@ jobs:
155
155
 
156
156
  sast-scan:
157
157
  name: SAST (Semgrep)
158
- runs-on: ubuntu-latest
158
+ runs-on: RunnerLandingZone
159
159
  steps:
160
160
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
161
161
  - run: docker run --rm -v "${{ github.workspace }}:/src" -w /src "$SEMGREP_IMAGE" semgrep scan --error --metrics=off --oss-only --disable-version-check --config p/default --config p/security-audit --config p/secrets --config p/csharp --config p/typescript --config p/react --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
162
162
 
163
163
  sbom:
164
164
  name: SBOM (CycloneDX)
165
- runs-on: ubuntu-latest
165
+ runs-on: RunnerLandingZone
166
166
  steps:
167
167
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
168
168
  - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
@@ -37,7 +37,7 @@ jobs:
37
37
  lint:
38
38
  name: Lint (Biome)
39
39
  if: github.event_name != 'schedule'
40
- runs-on: ubuntu-latest
40
+ runs-on: RunnerLandingZone
41
41
  steps:
42
42
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
43
43
  - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
@@ -50,7 +50,7 @@ jobs:
50
50
  web:
51
51
  name: Web (Next.js static export)
52
52
  if: github.event_name != 'schedule'
53
- runs-on: ubuntu-latest
53
+ runs-on: RunnerLandingZone
54
54
  steps:
55
55
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
56
56
  - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
@@ -73,7 +73,7 @@ jobs:
73
73
  name: E2E (Playwright)
74
74
  if: github.event_name != 'schedule'
75
75
  needs: web
76
- runs-on: ubuntu-latest
76
+ runs-on: RunnerLandingZone
77
77
  steps:
78
78
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
79
79
  - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
@@ -98,7 +98,7 @@ jobs:
98
98
  name: Desktop (Electron)
99
99
  if: github.event_name != 'schedule'
100
100
  needs: web
101
- runs-on: ubuntu-latest
101
+ runs-on: RunnerLandingZone
102
102
  steps:
103
103
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
104
104
  - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
@@ -122,7 +122,7 @@ jobs:
122
122
  mobile:
123
123
  name: Mobile (Capacitor)
124
124
  if: github.event_name != 'schedule'
125
- runs-on: ubuntu-latest
125
+ runs-on: RunnerLandingZone
126
126
  steps:
127
127
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
128
128
  - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
@@ -141,14 +141,14 @@ jobs:
141
141
 
142
142
  secret-scan:
143
143
  name: Secret scan (TruffleHog)
144
- runs-on: ubuntu-latest
144
+ runs-on: RunnerLandingZone
145
145
  steps:
146
146
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
147
147
  - run: docker run --rm -v "${{ github.workspace }}:/repo" "$TRUFFLEHOG_IMAGE" filesystem /repo --only-verified --fail --no-update
148
148
 
149
149
  deps-and-iac-scan:
150
150
  name: Dependencies and IaC (Trivy)
151
- runs-on: ubuntu-latest
151
+ runs-on: RunnerLandingZone
152
152
  steps:
153
153
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
154
154
  - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
@@ -162,14 +162,14 @@ jobs:
162
162
 
163
163
  sast-scan:
164
164
  name: SAST (Semgrep)
165
- runs-on: ubuntu-latest
165
+ runs-on: RunnerLandingZone
166
166
  steps:
167
167
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
168
168
  - run: docker run --rm -v "${{ github.workspace }}:/src" -w /src "$SEMGREP_IMAGE" semgrep scan --error --metrics=off --oss-only --disable-version-check --config p/default --config p/security-audit --config p/secrets --config p/typescript --config p/react --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
169
169
 
170
170
  sbom:
171
171
  name: SBOM (CycloneDX)
172
- runs-on: ubuntu-latest
172
+ runs-on: RunnerLandingZone
173
173
  steps:
174
174
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
175
175
  - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
@@ -12,7 +12,7 @@ permissions:
12
12
  jobs:
13
13
  publish:
14
14
  name: Publish release
15
- runs-on: ubuntu-latest
15
+ runs-on: RunnerLandingZone
16
16
  timeout-minutes: 10
17
17
  steps:
18
18
  - name: Create or update GitHub release
@@ -44,7 +44,7 @@ on:
44
44
  # substantive feedback. A custom job, not `on.steps`, because the prompt needs these values.
45
45
  jobs:
46
46
  subject:
47
- runs-on: ubuntu-latest
47
+ runs-on: RunnerLandingZone
48
48
  permissions:
49
49
  contents: read
50
50
  issues: read
@@ -113,7 +113,7 @@ jobs:
113
113
  reserve:
114
114
  needs: subject
115
115
  if: needs.subject.outputs.found == 'true' && needs.subject.outputs.issue != ''
116
- runs-on: ubuntu-latest
116
+ runs-on: RunnerLandingZone
117
117
  permissions:
118
118
  contents: read
119
119
  issues: write
@@ -145,7 +145,7 @@ jobs:
145
145
  if: >
146
146
  needs.agent.result == 'success' &&
147
147
  needs.safe_outputs.result == 'success'
148
- runs-on: ubuntu-latest
148
+ runs-on: RunnerLandingZone
149
149
  permissions:
150
150
  contents: write
151
151
  issues: write
@@ -174,7 +174,7 @@ jobs:
174
174
  always() &&
175
175
  needs.subject.outputs.found == 'true' &&
176
176
  needs.agent.result != 'success'
177
- runs-on: ubuntu-latest
177
+ runs-on: RunnerLandingZone
178
178
  permissions:
179
179
  contents: read
180
180
  issues: write
@@ -213,8 +213,8 @@ jobs:
213
213
 
214
214
  if: needs.subject.outputs.found == 'true'
215
215
 
216
- runs-on: ubuntu-latest
217
- runs-on-slim: ubuntu-latest
216
+ runs-on: RunnerLandingZone
217
+ runs-on-slim: RunnerLandingZone
218
218
 
219
219
  secrets:
220
220
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -42,8 +42,8 @@ on:
42
42
  query: "is:issue is:open label:audit"
43
43
  max: 3
44
44
 
45
- runs-on: ubuntu-latest
46
- runs-on-slim: ubuntu-latest
45
+ runs-on: RunnerLandingZone
46
+ runs-on-slim: RunnerLandingZone
47
47
 
48
48
  secrets:
49
49
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -76,7 +76,7 @@ jobs:
76
76
  needs.agent.result == 'success' &&
77
77
  needs.safe_outputs.result == 'success' &&
78
78
  needs.safe_outputs.outputs.process_safe_outputs_processed_count != '0'
79
- runs-on: ubuntu-latest
79
+ runs-on: RunnerLandingZone
80
80
  permissions:
81
81
  contents: read
82
82
  issues: write
@@ -45,7 +45,7 @@ on:
45
45
 
46
46
  jobs:
47
47
  eligibility:
48
- runs-on: ubuntu-latest
48
+ runs-on: RunnerLandingZone
49
49
  permissions:
50
50
  issues: read
51
51
  outputs:
@@ -72,7 +72,7 @@ jobs:
72
72
  reserve:
73
73
  needs: eligibility
74
74
  if: needs.eligibility.outputs.eligible == 'true'
75
- runs-on: ubuntu-latest
75
+ runs-on: RunnerLandingZone
76
76
  permissions:
77
77
  contents: read
78
78
  issues: write
@@ -104,7 +104,7 @@ jobs:
104
104
  if: >
105
105
  needs.agent.result == 'success' &&
106
106
  needs.safe_outputs.result == 'success'
107
- runs-on: ubuntu-latest
107
+ runs-on: RunnerLandingZone
108
108
  permissions:
109
109
  contents: read
110
110
  issues: write
@@ -140,7 +140,7 @@ jobs:
140
140
  always() &&
141
141
  needs.eligibility.outputs.eligible == 'true' &&
142
142
  needs.agent.result != 'success'
143
- runs-on: ubuntu-latest
143
+ runs-on: RunnerLandingZone
144
144
  permissions:
145
145
  contents: read
146
146
  issues: write
@@ -179,8 +179,8 @@ jobs:
179
179
 
180
180
  if: inputs.issue-number != '' && needs.eligibility.outputs.eligible == 'true'
181
181
 
182
- runs-on: ubuntu-latest
183
- runs-on-slim: ubuntu-latest
182
+ runs-on: RunnerLandingZone
183
+ runs-on-slim: RunnerLandingZone
184
184
 
185
185
  secrets:
186
186
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -43,7 +43,7 @@ on:
43
43
 
44
44
  jobs:
45
45
  eligibility:
46
- runs-on: ubuntu-latest
46
+ runs-on: RunnerLandingZone
47
47
  permissions:
48
48
  issues: read
49
49
  outputs:
@@ -70,7 +70,7 @@ jobs:
70
70
  reserve:
71
71
  needs: eligibility
72
72
  if: needs.eligibility.outputs.eligible == 'true'
73
- runs-on: ubuntu-latest
73
+ runs-on: RunnerLandingZone
74
74
  permissions:
75
75
  contents: read
76
76
  issues: write
@@ -108,7 +108,7 @@ jobs:
108
108
  if: >
109
109
  needs.agent.result == 'success' &&
110
110
  needs.safe_outputs.result == 'success'
111
- runs-on: ubuntu-latest
111
+ runs-on: RunnerLandingZone
112
112
  permissions:
113
113
  contents: read
114
114
  issues: write
@@ -144,7 +144,7 @@ jobs:
144
144
  always() &&
145
145
  needs.eligibility.outputs.eligible == 'true' &&
146
146
  needs.agent.result != 'success'
147
- runs-on: ubuntu-latest
147
+ runs-on: RunnerLandingZone
148
148
  permissions:
149
149
  contents: read
150
150
  issues: write
@@ -183,8 +183,8 @@ jobs:
183
183
 
184
184
  if: inputs.issue-number != '' && needs.eligibility.outputs.eligible == 'true'
185
185
 
186
- runs-on: ubuntu-latest
187
- runs-on-slim: ubuntu-latest
186
+ runs-on: RunnerLandingZone
187
+ runs-on-slim: RunnerLandingZone
188
188
 
189
189
  secrets:
190
190
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -239,23 +239,29 @@ timeout-minutes: 90
239
239
  a. Load the `ob-plan-goal` skill. It defines a mandatory, gate-sequenced pipeline:
240
240
  `explore · propose · apply · verify · archive · evidence · output · report`
241
241
 
242
- b. Execute every phase in order. Each phase loads its own sub-skill (`ob-plan-explore`,
242
+ b. **Refined-issue fast path:** If the issue context at `${{ env.ISSUE_CONTEXT_PATH }}`
243
+ already contains structured acceptance criteria (e.g. "## Acceptance criteria",
244
+ "### Scenario:", Gherkin blocks), affected artifacts, and design decisions, the
245
+ `ob-plan-goal` skill will skip the explore and propose phases and go directly to
246
+ apply. Do not override this: re-exploring a pre-refined issue wastes tokens.
247
+
248
+ c. Execute every phase in order. Each phase loads its own sub-skill (`ob-plan-explore`,
243
249
  `ob-plan-propose`, `ob-plan-apply`, `ob-repo-verify`, `ob-plan-archive`,
244
- `ob-ops-evidence`) and owns its procedure. You must not skip a phase, merge phases,
245
- or replace the pipeline with your own ad-hoc checklist.
250
+ `ob-ops-evidence`) and owns its procedure. You must not skip a phase unless the
251
+ pipeline's refined-issue detection says to.
246
252
 
247
- c. The `apply` phase uses `ob-plan-apply` which delegates implementation to specialist
253
+ d. The `apply` phase uses `ob-plan-apply` which delegates implementation to specialist
248
254
  subagent waves. Let it own worker resolution, concurrency, and retry , do not
249
255
  implement the tasks yourself unless `ob-plan-apply` instructs you to.
250
256
 
251
- d. Implement only what the issue asks for: a vague sentence is not licence to redesign
257
+ e. Implement only what the issue asks for: a vague sentence is not licence to redesign
252
258
  a module. Never read outside this repository root. The issue context at
253
259
  `${{ env.ISSUE_CONTEXT_PATH }}` defines acceptance criteria that the pipeline must
254
260
  satisfy.
255
261
 
256
- e. Follow repository documentation and established conventions. Keep changes focused,
257
- protect secrets, do not bypass checks, and do not modify generated files unless the issue requires it.
258
- Adhere to ${{ env.REPO_RULES }}.
262
+ f. Follow repository documentation and established conventions. Keep changes focused,
263
+ protect secrets, do not bypass checks, and do not modify generated files unless the issue requires it.
264
+ Adhere to ${{ env.REPO_RULES }}.
259
265
 
260
266
  4. Verify before you conclude. From the repository root:
261
267
 
@@ -57,7 +57,7 @@ on:
57
57
  # values and `on.steps` outputs do not reach the agent job.
58
58
  jobs:
59
59
  subject:
60
- runs-on: ubuntu-latest
60
+ runs-on: RunnerLandingZone
61
61
  permissions:
62
62
  contents: read
63
63
  issues: read
@@ -86,7 +86,7 @@ jobs:
86
86
  protected_changes:
87
87
  needs: subject
88
88
  if: needs.subject.outputs.found == 'true'
89
- runs-on: ubuntu-latest
89
+ runs-on: RunnerLandingZone
90
90
  permissions:
91
91
  pull-requests: read
92
92
  outputs:
@@ -119,7 +119,7 @@ jobs:
119
119
  review_required:
120
120
  needs: [subject, protected_changes]
121
121
  if: needs.subject.outputs.found == 'true' && needs.protected_changes.outputs.requires_review == 'true'
122
- runs-on: ubuntu-latest
122
+ runs-on: RunnerLandingZone
123
123
  permissions:
124
124
  contents: read
125
125
  issues: write
@@ -162,7 +162,7 @@ jobs:
162
162
  reserve:
163
163
  needs: subject
164
164
  if: needs.subject.outputs.found == 'true'
165
- runs-on: ubuntu-latest
165
+ runs-on: RunnerLandingZone
166
166
  permissions:
167
167
  contents: read
168
168
  issues: write
@@ -195,20 +195,6 @@ jobs:
195
195
  else
196
196
  echo "has_conflicts=false" >> "$GITHUB_OUTPUT"
197
197
  fi
198
- - name: Mark the issue as in progress
199
- if: needs.subject.outputs.conclusion == 'failure'
200
- uses: ./.github/actions/add-issue-labels
201
- with:
202
- token: ${{ steps.app-token.outputs.token }}
203
- issue-number: ${{ needs.subject.outputs.issue }}
204
- labels: ${{ env.WORKING_LABEL }}
205
- - name: Clear the human-needed flag
206
- if: needs.subject.outputs.conclusion == 'failure'
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.REVIEW_LABEL }}
212
198
  - name: Comment on issue - problems found, solving them
213
199
  if: needs.subject.outputs.conclusion == 'failure'
214
200
  uses: ./.github/actions/create-issue-comment
@@ -217,7 +203,7 @@ jobs:
217
203
  issue-number: ${{ needs.subject.outputs.issue }}
218
204
  body: |
219
205
  ${{ env.GATE_MARKER }}
220
- Problems found in PR #${{ needs.subject.outputs.pr }}. ${{ steps.conflicts.outputs.has_conflicts == 'true' && 'Merge conflicts detected.' || '' }} CI concluded with **${{ needs.subject.outputs.conclusion }}**.
206
+ Problems found in PR #${{ needs.subject.outputs.pr }}. ${{ steps.conflicts.outputs.has_conflicts == 'true' && 'Merge conflicts detected.' || 'CI failed.' }}
221
207
  Bot is working on fixing it.
222
208
  conclude:
223
209
  needs: [activation, subject, protected_changes, agent, safe_outputs]
@@ -225,7 +211,7 @@ jobs:
225
211
  needs.agent.result == 'success' &&
226
212
  needs.safe_outputs.result == 'success' &&
227
213
  needs.protected_changes.outputs.requires_review != 'true'
228
- runs-on: ubuntu-latest
214
+ runs-on: RunnerLandingZone
229
215
  permissions:
230
216
  contents: write
231
217
  issues: write
@@ -264,7 +250,7 @@ jobs:
264
250
  needs.subject.outputs.found == 'true' &&
265
251
  needs.protected_changes.outputs.requires_review != 'true' &&
266
252
  needs.agent.result != 'success'
267
- runs-on: ubuntu-latest
253
+ runs-on: RunnerLandingZone
268
254
  permissions:
269
255
  contents: read
270
256
  issues: write
@@ -308,8 +294,8 @@ jobs:
308
294
 
309
295
  if: needs.subject.outputs.found == 'true' && needs.protected_changes.outputs.requires_review != 'true'
310
296
 
311
- runs-on: ubuntu-latest
312
- runs-on-slim: ubuntu-latest
297
+ runs-on: RunnerLandingZone
298
+ runs-on-slim: RunnerLandingZone
313
299
 
314
300
  secrets:
315
301
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -56,8 +56,8 @@ on:
56
56
  query: "is:issue is:open label:proposed -label:refine -label:implement"
57
57
  max: 1
58
58
 
59
- runs-on: ubuntu-latest
60
- runs-on-slim: ubuntu-latest
59
+ runs-on: RunnerLandingZone
60
+ runs-on-slim: RunnerLandingZone
61
61
 
62
62
  secrets:
63
63
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -156,7 +156,7 @@ jobs:
156
156
  # Circuit breaker. A guard job that only reports; the agent's own `if:` is what stops the
157
157
  # run, because a `needs` job succeeding with a false output does not gate its dependents.
158
158
  capacity:
159
- runs-on: ubuntu-latest
159
+ runs-on: RunnerLandingZone
160
160
  permissions:
161
161
  issues: read
162
162
  outputs:
@@ -185,7 +185,7 @@ jobs:
185
185
  needs.agent.result == 'success' &&
186
186
  needs.safe_outputs.result == 'success' &&
187
187
  needs.safe_outputs.outputs.process_safe_outputs_processed_count != '0'
188
- runs-on: ubuntu-latest
188
+ runs-on: RunnerLandingZone
189
189
  permissions:
190
190
  contents: read
191
191
  issues: write
@@ -225,7 +225,7 @@ safe-outputs:
225
225
  # (release notes) and then files an issue, so it is the only one where a prompt-injection
226
226
  # attempt has somewhere to go.
227
227
  threat-detection:
228
- runs-on: ubuntu-latest
228
+ runs-on: RunnerLandingZone
229
229
  create-issue:
230
230
  max: 1
231
231
 
@@ -53,7 +53,7 @@ on:
53
53
 
54
54
  jobs:
55
55
  reserve:
56
- runs-on: ubuntu-latest
56
+ runs-on: RunnerLandingZone
57
57
  permissions:
58
58
  contents: read
59
59
  issues: write
@@ -86,7 +86,7 @@ jobs:
86
86
  always() &&
87
87
  needs.agent.result == 'success' &&
88
88
  needs.safe_outputs.result == 'success'
89
- runs-on: ubuntu-latest
89
+ runs-on: RunnerLandingZone
90
90
  permissions:
91
91
  contents: read
92
92
  outputs:
@@ -116,7 +116,7 @@ jobs:
116
116
  needs.agent.result == 'success' &&
117
117
  needs.safe_outputs.result == 'success' &&
118
118
  needs.validate_output.outputs.valid == 'true'
119
- runs-on: ubuntu-latest
119
+ runs-on: RunnerLandingZone
120
120
  permissions:
121
121
  contents: read
122
122
  issues: write
@@ -181,7 +181,7 @@ jobs:
181
181
  needs.safe_outputs.result != 'success' ||
182
182
  needs.validate_output.outputs.valid != 'true'
183
183
  )
184
- runs-on: ubuntu-latest
184
+ runs-on: RunnerLandingZone
185
185
  permissions:
186
186
  contents: read
187
187
  issues: write
@@ -218,8 +218,8 @@ jobs:
218
218
 
219
219
  if: inputs.issue-number != ''
220
220
 
221
- runs-on: ubuntu-latest
222
- runs-on-slim: ubuntu-latest
221
+ runs-on: RunnerLandingZone
222
+ runs-on-slim: RunnerLandingZone
223
223
 
224
224
  secrets:
225
225
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -1,5 +1,8 @@
1
1
  # Human adds implement/refine/direct label → validates permission → bot adds bot-working
2
2
  # This ensures the bot is the actor for all agentic workflows.
3
+ #
4
+ # IMPORTANT: Only triggers for HUMAN actors. When the bot transitions refine→implement,
5
+ # it adds bot-working itself, so authorize-bot-work must not fire again.
3
6
  name: "Authorize Bot Work"
4
7
 
5
8
  on:
@@ -11,13 +14,15 @@ permissions:
11
14
 
12
15
  jobs:
13
16
  authorize:
14
- # Only trigger for implement/refine/direct labels, and only if bot-working is NOT already present
17
+ # Only trigger for implement/refine/direct labels from HUMANS (not bots),
18
+ # and only if bot-working is NOT already present
15
19
  if: >
16
20
  (github.event.label.name == 'implement' ||
17
21
  github.event.label.name == 'refine' ||
18
22
  github.event.label.name == 'direct') &&
19
- !contains(github.event.issue.labels.*.name, 'bot-working')
20
- runs-on: ubuntu-latest
23
+ !contains(github.event.issue.labels.*.name, 'bot-working') &&
24
+ !endsWith(github.actor, '[bot]')
25
+ runs-on: RunnerLandingZone
21
26
  timeout-minutes: 5
22
27
  permissions:
23
28
  contents: read
@@ -31,13 +36,6 @@ jobs:
31
36
  run: |
32
37
  set -euo pipefail
33
38
 
34
- # Bots don't need validation - they're already authorized
35
- if [[ "$ACTOR" == *"[bot]"* ]]; then
36
- echo "authorized=true" >> "$GITHUB_OUTPUT"
37
- echo "::notice::Bot actor ($ACTOR) - skipping permission check"
38
- exit 0
39
- fi
40
-
41
39
  permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$ACTOR/permission" \
42
40
  --jq '.permission' 2>/dev/null || echo "none")
43
41
 
@@ -118,7 +118,7 @@ permissions:
118
118
  jobs:
119
119
  # Public repositories must check repository permission before an event can start a model.
120
120
  authorize:
121
- runs-on: ubuntu-latest
121
+ runs-on: RunnerLandingZone
122
122
  timeout-minutes: 5
123
123
  permissions:
124
124
  contents: read
@@ -167,7 +167,7 @@ jobs:
167
167
  esac
168
168
 
169
169
  classify:
170
- runs-on: ubuntu-latest
170
+ runs-on: RunnerLandingZone
171
171
  timeout-minutes: 5
172
172
  permissions:
173
173
  contents: read
@@ -322,7 +322,7 @@ jobs:
322
322
  if: >
323
323
  needs.classify.outputs.route == 'bot-approve' &&
324
324
  (github.actor == 'app/github-actions' || github.actor == 'platform-devbox[bot]')
325
- runs-on: ubuntu-latest
325
+ runs-on: RunnerLandingZone
326
326
  timeout-minutes: 5
327
327
  permissions:
328
328
  actions: write
@@ -353,7 +353,7 @@ jobs:
353
353
  reconcile-bot-pr-runs:
354
354
  needs: classify
355
355
  if: needs.classify.outputs.route == 'reconcile-bot-pr-runs'
356
- runs-on: ubuntu-latest
356
+ runs-on: RunnerLandingZone
357
357
  timeout-minutes: 5
358
358
  permissions:
359
359
  contents: read
@@ -387,7 +387,7 @@ jobs:
387
387
  jq -r --arg repo "$REPO" '
388
388
  .[]
389
389
  | select(
390
- .user.login == "github-actions[bot]"
390
+ (.user.login == "github-actions[bot]" or .user.login == "app/github-actions" or .user.login == "platform-devbox[bot]")
391
391
  and .head.repo.full_name == $repo
392
392
  and .mergeable_state != "dirty"
393
393
  )
@@ -406,12 +406,25 @@ jobs:
406
406
  gh api "repos/$REPO/actions/runs/$run_id/approve" --method POST
407
407
  done
408
408
 
409
+ # Check if merge-gate already ran for this PR (comment on PR or linked issue)
409
410
  gate_recorded=$(gh api "repos/$REPO/issues/$pr_number/comments?per_page=100" \
410
411
  --jq 'any(.[]; .body | contains("<!-- agent-merge-gate -->"))')
411
412
  if [ "$gate_recorded" = "true" ]; then
413
+ echo "Merge gate already recorded for PR #$pr_number, skipping"
412
414
  continue
413
415
  fi
414
416
 
417
+ # Also check the linked issue comments if the PR closes an issue
418
+ linked_issue=$(gh pr view "$pr_number" --repo "$REPO" --json closingIssuesReferences --jq '.closingIssuesReferences[0].number // empty' 2>/dev/null || true)
419
+ if [ -n "$linked_issue" ]; then
420
+ gate_recorded=$(gh api "repos/$REPO/issues/$linked_issue/comments?per_page=100" \
421
+ --jq 'any(.[]; .body | contains("<!-- agent-merge-gate -->"))')
422
+ if [ "$gate_recorded" = "true" ]; then
423
+ echo "Merge gate already recorded on issue #$linked_issue for PR #$pr_number, skipping"
424
+ continue
425
+ fi
426
+ fi
427
+
415
428
  ci=$(gh api "repos/$REPO/actions/runs?branch=$branch&per_page=100" \
416
429
  --jq '[.workflow_runs[]
417
430
  | select(
@@ -439,7 +452,7 @@ jobs:
439
452
  audit-close:
440
453
  needs: classify
441
454
  if: needs.classify.outputs.route == 'audit-close'
442
- runs-on: ubuntu-latest
455
+ runs-on: RunnerLandingZone
443
456
  timeout-minutes: 15
444
457
  concurrency:
445
458
  group: audit-close
@@ -459,7 +472,7 @@ jobs:
459
472
  cleanup-artifacts:
460
473
  needs: classify
461
474
  if: needs.classify.outputs.route == 'cleanup-artifacts' && !github.event.repository.fork
462
- runs-on: ubuntu-latest
475
+ runs-on: RunnerLandingZone
463
476
  timeout-minutes: 15
464
477
  concurrency:
465
478
  group: cleanup-artifacts
@@ -480,7 +493,7 @@ jobs:
480
493
  stale-recovery:
481
494
  needs: classify
482
495
  if: needs.classify.outputs.route == 'stale-recovery'
483
- runs-on: ubuntu-latest
496
+ runs-on: RunnerLandingZone
484
497
  timeout-minutes: 15
485
498
  concurrency:
486
499
  group: stale-recovery
@@ -503,7 +516,7 @@ jobs:
503
516
  validate:
504
517
  needs: classify
505
518
  if: needs.classify.outputs.route == 'validate'
506
- runs-on: ubuntu-latest
519
+ runs-on: RunnerLandingZone
507
520
  timeout-minutes: 10
508
521
  permissions:
509
522
  contents: read
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainconceptsplatform/workflows",
3
- "version": "0.4.15",
3
+ "version": "0.4.20",
4
4
  "description": "Install and update Platform GitHub agentic workflows.",
5
5
  "keywords": [
6
6
  "github-actions",