@plainconceptsplatform/workflows 0.4.33 → 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 (32) 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 +27 -0
  5. package/dist/catalog-listing.test.js +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.test.js +6 -6
  8. package/dist/route-processing.test.js +12 -4
  9. package/dist/tui.test.js +8 -8
  10. package/dist/workflow-catalog.d.ts +1 -1
  11. package/dist/workflow-catalog.js +2 -0
  12. package/loops/actions/classify-route/action.yml +3 -0
  13. package/loops/actions/classify-route/classify-route.sh +38 -3
  14. package/loops/actions/load-issue-context/action.yml +2 -0
  15. package/loops/actions/validate-merge-gate-output/action.yml +40 -0
  16. package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +34 -0
  17. package/loops/actions/validate-review-output/action.yml +35 -0
  18. package/loops/actions/validate-review-output/validate-review-output.sh +30 -0
  19. package/loops/actions/validate-triage-output/action.yml +36 -0
  20. package/loops/actions/validate-triage-output/validate-triage-output.sh +36 -0
  21. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +32 -2
  22. package/loops/workflows/agent-apply-review.md +89 -19
  23. package/loops/workflows/agent-audit.md +2 -2
  24. package/loops/workflows/agent-direct.md +14 -2
  25. package/loops/workflows/agent-implement.md +56 -16
  26. package/loops/workflows/agent-merge-gate.md +159 -60
  27. package/loops/workflows/agent-propose.md +1 -1
  28. package/loops/workflows/agent-refine.md +45 -6
  29. package/loops/workflows/agent-triage.md +439 -0
  30. package/loops/workflows/authorize-bot-work.yml +2 -1
  31. package/loops/workflows/work-router.yml +89 -8
  32. package/package.json +43 -42
@@ -68,6 +68,7 @@ jobs:
68
68
  pr: ${{ steps.subject.outputs.pr }}
69
69
  issue: ${{ steps.subject.outputs.issue }}
70
70
  conclusion: ${{ steps.subject.outputs.conclusion }}
71
+ review_blocked: ${{ steps.review.outputs.review_blocked }}
71
72
  steps:
72
73
  - name: Checkout workflow actions
73
74
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -82,6 +83,16 @@ jobs:
82
83
  ci-conclusion: ${{ inputs.ci-conclusion }}
83
84
  linked-issue: ${{ inputs.linked-issue }}
84
85
  require-label: ${{ env.IMPLEMENT_LABEL }}
86
+ - name: Block a pull request with requested changes
87
+ id: review
88
+ env:
89
+ GH_TOKEN: ${{ github.token }}
90
+ REPO: ${{ github.repository }}
91
+ PR: ${{ steps.subject.outputs.pr }}
92
+ run: |
93
+ set -euo pipefail
94
+ decision=$(gh pr view "$PR" --repo "$REPO" --json reviewDecision --jq '.reviewDecision')
95
+ echo "review_blocked=$([ "$decision" = 'CHANGES_REQUESTED' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
85
96
 
86
97
  protected_changes:
87
98
  needs: subject
@@ -102,7 +113,7 @@ jobs:
102
113
  run: |
103
114
  set -euo pipefail
104
115
  files=$(gh api --paginate "repos/$REPO/pulls/$PR/files?per_page=100" --jq '.[].filename')
105
- protected=$(printf '%s\n' "$files" | grep -E '^(\.|AGENTS\.md$|ARCHITECTURE\.md$|DESIGN\.md$|opencode\.jsonc$|package\.json$|pnpm-lock\.yaml$|Directory\.Packages\.props$|global\.json$)' || true)
116
+ protected=$(printf '%s\n' "$files" | grep -E '^(\.|AGENTS\.md$|ARCHITECTURE\.md$|opencode\.jsonc$|package\.json$|pnpm-lock\.yaml$|Directory\.Packages\.props$|global\.json$)' || true)
106
117
 
107
118
  if [ -n "$protected" ]; then
108
119
  echo "requires_review=true" >> "$GITHUB_OUTPUT"
@@ -118,35 +129,42 @@ jobs:
118
129
 
119
130
  review_required:
120
131
  needs: [subject, protected_changes]
121
- if: needs.subject.outputs.found == 'true' && needs.protected_changes.outputs.requires_review == 'true'
132
+ # gh-aw makes the agent depend on custom jobs. Keep this job successful when
133
+ # there are no protected files instead of skipping it and blocking remediation.
134
+ if: always() && needs.subject.outputs.found == 'true'
122
135
  runs-on: RunnerLandingZone
123
136
  permissions:
124
137
  contents: read
125
138
  issues: write
126
139
  steps:
127
140
  - name: Checkout workflow actions
141
+ if: needs.protected_changes.outputs.requires_review == 'true'
128
142
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
129
143
  with:
130
144
  persist-credentials: false
131
145
  - name: Create bot token
146
+ if: needs.protected_changes.outputs.requires_review == 'true'
132
147
  id: app-token
133
148
  uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
134
149
  with:
135
150
  client-id: ${{ secrets.BOT_APP_ID }}
136
151
  private-key: ${{ secrets.BOT_PRIVATE_KEY }}
137
152
  - name: Release the issue
153
+ if: needs.protected_changes.outputs.requires_review == 'true'
138
154
  uses: ./.github/actions/remove-issue-labels
139
155
  with:
140
156
  token: ${{ steps.app-token.outputs.token }}
141
157
  issue-number: ${{ needs.subject.outputs.issue }}
142
158
  labels: ${{ env.WORKING_LABEL }}
143
159
  - name: Flag human review
160
+ if: needs.protected_changes.outputs.requires_review == 'true'
144
161
  uses: ./.github/actions/add-issue-labels
145
162
  with:
146
163
  token: ${{ steps.app-token.outputs.token }}
147
164
  issue-number: ${{ needs.subject.outputs.issue }}
148
165
  labels: ${{ env.REVIEW_LABEL }}
149
166
  - name: Explain the merge hold
167
+ if: needs.protected_changes.outputs.requires_review == 'true'
150
168
  uses: ./.github/actions/create-issue-comment
151
169
  with:
152
170
  token: ${{ steps.app-token.outputs.token }}
@@ -167,6 +185,8 @@ jobs:
167
185
  contents: read
168
186
  issues: write
169
187
  pull-requests: read
188
+ outputs:
189
+ has_conflicts: ${{ steps.conflicts.outputs.has_conflicts || 'false' }}
170
190
  steps:
171
191
  - name: Checkout workflow actions
172
192
  if: needs.subject.outputs.conclusion == 'failure'
@@ -205,11 +225,41 @@ jobs:
205
225
  ${{ env.GATE_MARKER }}
206
226
  Problems found in PR #${{ needs.subject.outputs.pr }}. ${{ steps.conflicts.outputs.has_conflicts == 'true' && 'Merge conflicts detected.' || 'CI failed.' }}
207
227
  Bot is working on fixing it.
228
+ validate_output:
229
+ needs: [activation, agent, safe_outputs]
230
+ if: >
231
+ always() &&
232
+ needs.agent.result == 'success' &&
233
+ needs.safe_outputs.result == 'success'
234
+ runs-on: RunnerLandingZone
235
+ permissions:
236
+ contents: read
237
+ outputs:
238
+ valid: ${{ steps.validate.outputs.valid }}
239
+ outcome: ${{ steps.validate.outputs.outcome }}
240
+ steps:
241
+ - name: Checkout workflow actions
242
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
243
+ with:
244
+ persist-credentials: false
245
+ - name: Download agent output
246
+ id: output
247
+ uses: ./.github/actions/download-agent-output
248
+ with:
249
+ artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
250
+ - name: Validate merge-gate outcome
251
+ id: validate
252
+ uses: ./.github/actions/validate-merge-gate-output
253
+ with:
254
+ output-file: ${{ steps.output.outputs.output-file }}
255
+ issue-number: ${{ needs.subject.outputs.issue }}
256
+ ci-conclusion: ${{ needs.subject.outputs.conclusion }}
208
257
  conclude:
209
- needs: [activation, subject, protected_changes, agent, safe_outputs]
258
+ needs: [activation, subject, protected_changes, agent, safe_outputs, validate_output]
210
259
  if: >
211
260
  needs.agent.result == 'success' &&
212
- needs.safe_outputs.result == 'success' &&
261
+ needs.safe_outputs.result == 'success' &&
262
+ needs.validate_output.outputs.valid == 'true' &&
213
263
  needs.protected_changes.outputs.requires_review != 'true'
214
264
  runs-on: RunnerLandingZone
215
265
  permissions:
@@ -240,16 +290,57 @@ jobs:
240
290
  with:
241
291
  artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
242
292
  token: ${{ steps.app-token.outputs.token }}
243
- merge-pull-request: 'true'
244
293
  push-to-branch: 'true'
245
- close-issues: 'true'
294
+ apply-labels: 'false'
295
+ - name: Merge approved pull request
296
+ if: needs.validate_output.outputs.outcome == 'merge'
297
+ env:
298
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
299
+ REPO: ${{ github.repository }}
300
+ PR: ${{ needs.subject.outputs.pr }}
301
+ run: |
302
+ set -euo pipefail
303
+ head_sha=$(gh pr view "$PR" --repo "$REPO" --json headRefOid --jq '.headRefOid')
304
+ gh pr merge "$PR" --repo "$REPO" --squash --match-head-commit "$head_sha"
305
+ - name: Release remediated issue
306
+ if: needs.validate_output.outputs.outcome == 'remediated'
307
+ uses: ./.github/actions/remove-issue-labels
308
+ with:
309
+ token: ${{ steps.app-token.outputs.token }}
310
+ issue-number: ${{ needs.subject.outputs.issue }}
311
+ labels: ${{ env.WORKING_LABEL }}
312
+ - name: Flag review outcome
313
+ if: needs.validate_output.outputs.outcome == 'review'
314
+ uses: ./.github/actions/add-issue-labels
315
+ with:
316
+ token: ${{ steps.app-token.outputs.token }}
317
+ issue-number: ${{ needs.subject.outputs.issue }}
318
+ labels: ${{ env.REVIEW_LABEL }}
319
+ - name: Release review outcome
320
+ if: needs.validate_output.outputs.outcome == 'review'
321
+ uses: ./.github/actions/remove-issue-labels
322
+ with:
323
+ token: ${{ steps.app-token.outputs.token }}
324
+ issue-number: ${{ needs.subject.outputs.issue }}
325
+ labels: ${{ env.WORKING_LABEL }}
326
+ - name: Clear merged issue labels
327
+ if: needs.validate_output.outputs.outcome == 'merge'
328
+ uses: ./.github/actions/remove-issue-labels
329
+ with:
330
+ token: ${{ steps.app-token.outputs.token }}
331
+ issue-number: ${{ needs.subject.outputs.issue }}
332
+ labels: ${{ env.IMPLEMENT_LABEL }},${{ env.WORKING_LABEL }},${{ env.REVIEW_LABEL }}
246
333
  incomplete:
247
- needs: [subject, protected_changes, agent, safe_outputs]
334
+ needs: [subject, protected_changes, agent, safe_outputs, validate_output]
248
335
  if: >
249
336
  always() &&
250
337
  needs.subject.outputs.found == 'true' &&
251
338
  needs.protected_changes.outputs.requires_review != 'true' &&
252
- needs.agent.result != 'success'
339
+ (
340
+ needs.agent.result != 'success' ||
341
+ needs.safe_outputs.result != 'success' ||
342
+ needs.validate_output.outputs.valid != 'true'
343
+ )
253
344
  runs-on: RunnerLandingZone
254
345
  permissions:
255
346
  contents: read
@@ -289,10 +380,12 @@ jobs:
289
380
  [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
290
381
 
291
382
  agent:
292
- needs: protected_changes
293
- if: needs.protected_changes.outputs.requires_review != 'true'
383
+ # The top-level guard reads both outputs. GitHub Actions does not make a
384
+ # dependency's dependencies available through `needs` transitively.
385
+ needs: [subject, protected_changes]
386
+ if: always() && needs.protected_changes.outputs.requires_review != 'true' && needs.subject.outputs.review_blocked != 'true'
294
387
 
295
- if: needs.subject.outputs.found == 'true' && needs.protected_changes.outputs.requires_review != 'true'
388
+ if: always() && needs.subject.outputs.found == 'true' && needs.protected_changes.outputs.requires_review != 'true' && needs.subject.outputs.review_blocked != 'true'
296
389
 
297
390
  runs-on: RunnerLandingZone
298
391
  runs-on-slim: RunnerLandingZone
@@ -354,12 +447,9 @@ steps:
354
447
  safe-outputs:
355
448
  staged: true
356
449
  threat-detection: false
357
- merge-pull-request:
358
450
  push-to-pull-request-branch:
359
- close-issue:
360
451
  add-comment:
361
- add-labels:
362
- remove-labels:
452
+ target: "*"
363
453
 
364
454
  timeout-minutes: 60
365
455
  ---
@@ -378,19 +468,23 @@ timeout-minutes: 60
378
468
 
379
469
  3. Branch on the conclusion.
380
470
 
471
+ First check the issue context for `<!-- complexity: trivial -->`.
472
+
473
+ **If the trivial marker is present AND CI conclusion is success:**
474
+ Skip the risk assessment (step 4). Proceed directly to step 5 (merge).
475
+ The change is presentation-only and CI is green — no need for deep risk review.
476
+ Trivial changes do not touch business logic, data models, auth, or infrastructure by definition.
477
+
478
+ **If the trivial marker is absent OR CI is not success:**
479
+ Follow the normal branching below.
480
+
381
481
  - **success** → step 4.
382
- - **action_required** → CI did not run because the workflow needs approval.
383
- Call `remove_labels` to remove `bot-working` (item_number:
384
- ${{ needs.subject.outputs.issue }}), then `add_labels` to add `review`
385
- (item_number: ${{ needs.subject.outputs.issue }}), and `add_comment`
386
- (item_number: ${{ needs.subject.outputs.issue }}) saying CI requires
387
- workflow approval before it can run, and a maintainer must approve the
388
- pending run. Then stop , do not attempt to merge or fix.
482
+ - **action_required** → CI did not run because the workflow needs approval.
483
+ Proceed to review: explain that a maintainer must approve the pending run.
484
+ Do not attempt to merge or fix.
389
485
  - **failure** → step 7.
390
- - **cancelled, timed_out, or anything else** → call `add_comment` with
391
- `item_number: ${{ needs.subject.outputs.issue }}` saying CI did not produce a verdict,
392
- then `add_labels` to add `review` (item_number: the issue), and stop.
393
- A cancelled or unknown run is not evidence of anything.
486
+ - **cancelled, timed_out, or anything else** → proceed to review. A cancelled or unknown
487
+ run is not evidence of anything.
394
488
 
395
489
  Follow repository documentation and established conventions when assessing or remediating
396
490
  the pull request. Protect secrets, do not bypass checks, and keep remediation focused.
@@ -410,45 +504,47 @@ timeout-minutes: 60
410
504
  - The diff is materially larger than the issue implied.
411
505
  - You are simply not confident. Low confidence is itself a flag.
412
506
 
413
- 5. **Clean assessment** → call `merge_pull_request` (pr_number:
414
- ${{ needs.subject.outputs.pr }}), then `close_issue` (item_number:
415
- ${{ needs.subject.outputs.issue }}), then `remove_labels` (item_number:
416
- ${{ needs.subject.outputs.issue }}) to remove `implement`, `bot-working`, and `review`.
417
- Call `add_comment` (item_number: ${{ needs.subject.outputs.issue }}):
418
- `Auto-merged. PR #${{ needs.subject.outputs.pr }} closed.`
419
-
420
- 6. **Flagged** do not merge. Call `remove_labels` to remove `bot-working`
421
- (item_number: ${{ needs.subject.outputs.issue }}), then `add_labels` to add `review`
422
- (item_number: ${{ needs.subject.outputs.issue }}), and `add_comment`
423
- (item_number: ${{ needs.subject.outputs.issue }}) explaining exactly which criterion
424
- tripped and what a reviewer should look at. Then `add_comment`
425
- (item_number: ${{ needs.subject.outputs.pr }}) on the PR with the same message.
426
- Leave `implement` in place: the work is not finished until a human merges it.
427
-
428
- 7. **CI failed** read `/tmp/gh-aw/agent/failed-jobs.json` and
429
- `/tmp/gh-aw/agent/failed-logs.txt`, which are already on disk. Load only skills required to
430
- fix the actual cause. Run these verification commands before a push. Do not weaken a test,
431
- disable a check, or push an unverified guess.
507
+ 5. **Clean assessment** → select the `merge` verdict.
508
+
509
+ 6. **Flagged** select the `review` verdict. Do not merge. Explain exactly which criterion
510
+ tripped and what a reviewer should look at. Leave `implement` in place: the work is not
511
+ finished until a human merges it.
512
+
513
+ 7. **CI failed** → read `/tmp/gh-aw/agent/failed-jobs.json` and
514
+ `/tmp/gh-aw/agent/failed-logs.txt`, which are already on disk. Load only skills required to
515
+ fix the actual cause. Run these verification commands before a push. Do not weaken a test,
516
+ disable a check, or push an unverified guess.
517
+
518
+ **If `has_conflicts` is `true` (current value: `${{ needs.reserve.outputs.has_conflicts }}`):** You are already on the PR branch. Resolve the conflict;
519
+ it is not a reason to hand the PR to a human. Merge `origin/${{ github.event.repository.default_branch }}`
520
+ into the current branch, resolve every conflict deliberately, stage the resolutions, and
521
+ commit the merge. Then run verification and push the resulting branch update. Do not use
522
+ `--ours`, `--theirs`, or a blanket conflict-marker deletion without reviewing the intended
523
+ behavior from both sides.
432
524
 
433
525
  ```
434
526
  ${{ env.VERIFY_COMMANDS }}
435
527
  ```
436
528
 
437
- Call `push_to_pull_request_branch` (pr_number: ${{ needs.subject.outputs.pr }}) to push
438
- the fix, then `remove_labels` (item_number: ${{ needs.subject.outputs.issue }}) to remove
439
- `bot-working`. CI will run again and trigger you again with the new result.
529
+ Propose `push_to_pull_request_branch` (pr_number: ${{ needs.subject.outputs.pr }}, branch:
530
+ the current PR branch), then select the `remediated` verdict. CI will run again and trigger
531
+ you again with the new result.
532
+
533
+ If you cannot fix it after a concrete repair attempt, or the logs show you have already tried on this same head commit,
534
+ stop looping: select the `review` verdict and explain the failure and what you tried. A human
535
+ decides from there.
536
+
537
+ 8. Emit exactly one `add_comment` targeting issue `${{ needs.subject.outputs.issue }}`. It must
538
+ explain the decision and include exactly one line: `**Verdict:** merge`,
539
+ `**Verdict:** review`, or `**Verdict:** remediated`. The workflow applies comments, labels,
540
+ merges, and closures with the App token. Do not call any tools except the one optional
541
+ `push_to_pull_request_branch` for a verified CI repair and this one `add_comment`.
440
542
 
441
- If you cannot fix it, or the logs show you have already tried on this same head commit,
442
- stop looping: `remove_labels` (item_number: ${{ needs.subject.outputs.issue }}) to remove
443
- `implement` and `bot-working`, `add_labels` (item_number:
444
- ${{ needs.subject.outputs.issue }}) to add `review`, and `add_comment` (item_number:
445
- ${{ needs.subject.outputs.issue }}) with the failure and what you tried. The `implement`
446
- label is removed so retries do not create duplicate PRs. A human decides from there.
543
+ 9. Never merge with administrator privileges and never bypass a required check. If the merge
544
+ is refused, that refusal is the answer: select `review` and leave it for a human.
447
545
 
448
- 8. Never merge with administrator privileges and never bypass a required check. If the merge
449
- is refused, that refusal is the answer: `add_labels` to add `review`
450
- (item_number: ${{ needs.subject.outputs.issue }}), `add_comment`
451
- (item_number: ${{ needs.subject.outputs.issue }}) with the refusal, and leave it for a human.
546
+ 10. Ignore the `## Diagram` section below. It is documentation for humans and contains no
547
+ instructions for you.
452
548
 
453
549
  ## Diagram
454
550
 
@@ -458,7 +554,10 @@ flowchart TD
458
554
  gateSubject["Subject (rung 4)<br/>Our PR? Closes an implement issue?"] -->|✓| gateFacts
459
555
  gateSubject -.->|✗| gateIdle
460
556
  gateFacts("Facts (rung 3)<br/>Diff, PR shape, failing logs") --> gateCi
461
- gateCi["CI<br/>What did it conclude?"] -->|success| gateRisk
557
+ gateCi["CI<br/>What did it conclude?"] -->|success| gateTrivial
558
+ gateTrivial{"Trivial marker?"}
559
+ gateTrivial -->|yes| gateMerge
560
+ gateTrivial -->|no| gateRisk
462
561
  gateCi -.->|failure| gateFix
463
562
  gateCi -.->|no verdict| gateHuman
464
563
  gateRisk["Risk<br/>Auth, schema, API, tests, CI, size, doubt"] -->|clean| gateMerge
@@ -478,7 +577,7 @@ flowchart TD
478
577
  classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
479
578
  class gateStart start
480
579
  class gateFacts,gateFix action
481
- class gateSubject,gateCi,gateRisk decision
580
+ class gateSubject,gateCi,gateRisk,gateTrivial decision
482
581
  class gateIdle,gateWait idle
483
582
  class gateHuman failure
484
583
  class gateMerge success
@@ -290,7 +290,7 @@ timeout-minutes: 45
290
290
  Write all four, not just the winner. Next run reads this to avoid re-deriving the three that
291
291
  lost. A body containing only the winner makes this workflow amnesiac.
292
292
 
293
- **Section 2, The proposal:** Call skill("ob-plan-story") and refine the highest scorer into a
293
+ **Section 2, The proposal:** Call skill("pc-plan-story") and refine the highest scorer into a
294
294
  user story in Mike Cohn's As a / I want to / so that form, with Given/When/Then acceptance
295
295
  criteria, edge cases, and the likely files to change.
296
296
 
@@ -11,6 +11,7 @@ env:
11
11
  INITIAL_MODE: first
12
12
  RESPONSE_MODE: rerefine
13
13
  MAX_SELF_QUESTIONS: "5"
14
+ TRIVIAL_MARKER: "<!-- complexity: trivial -->"
14
15
  INCOMPLETE_COMMENT: "Automated refinement ended without an outcome. The refine label remains for a retry."
15
16
  SAFE_OUTPUT_COMMENT_PREFIX: "Refinement update"
16
17
  ISSUE_CONTEXT_PATH: /tmp/gh-aw/agent/issue-context.json
@@ -275,7 +276,7 @@ timeout-minutes: 40
275
276
  - On a `${{ env.RESPONSE_MODE }}` pass, incorporate only the supplied answers from the issue author or an
276
277
  assignee. Do not use answers from other commenters.
277
278
 
278
- 3. Explore before you write. Call skill("ob-plan-explore") and hold its stance for this step:
279
+ 3. Explore before you write. Call skill("pc-plan-explore") and hold its stance for this step:
279
280
  read-only, no plans, no files, no branches. You are only building understanding here, never
280
281
  producing artifacts.
281
282
 
@@ -304,9 +305,38 @@ timeout-minutes: 40
304
305
  longer changes your understanding. This exploration is internal working: never write your
305
306
  self-asked questions or their answers to the issue.
306
307
 
307
- 4. Before writing the story, verify coverage: list every work unit and confirm each one has
308
+ 4. **Classify the change complexity.** Based on your exploration, determine whether this is a
309
+ trivial change. A change is **trivial** if ALL of these are true:
310
+
311
+ - It touches 1-3 files: CSS, Tailwind classes, text labels, markup, or styling only
312
+ - No business logic: no services, controllers, domain models, calculations, validations
313
+ - No data model: no entities, migrations, DTOs, API contracts
314
+ - No security surface: no auth, authorization, secrets, tokens, permissions
315
+ - No infrastructure: no Bicep, Docker, CI, deploy configuration
316
+ - No cross-cutting: doesn't touch shared libraries or multi-team contracts
317
+
318
+ If ALL pass → **trivial path** (step 4a). If ANY fail → **standard path** (step 5).
319
+
320
+ **4a. Trivial path.** Skip `/plan-story`. Do not write Gherkin acceptance criteria or
321
+ Mermaid diagrams. Instead, prepare the replacement issue body as valid Markdown:
322
+
323
+ 1. `${{ env.TRIVIAL_MARKER }}`
324
+ 2. A short plain-English summary of what needs to change and why (2-3 sentences max)
325
+ 3. A simple checklist of concrete steps:
326
+ ```
327
+ ## Tasks
328
+ - [ ] Change X in file Y
329
+ - [ ] Verify Z
330
+ ```
331
+
332
+ No "As a / I want / so that" form. No Given/When/Then. No Mermaid. Just the marker,
333
+ the summary, and the checklist.
334
+
335
+ Load `@humanizer` and prepare the replacement issue body, then go directly to step 6.
336
+
337
+ 5. Before writing the story, verify coverage: list every work unit and confirm each one has
308
338
  exploration findings concrete enough for acceptance criteria. If any unit is missing, go back
309
- and explore it now. Then call skill("ob-plan-story") and run `/plan-story` for the issue,
339
+ and explore it now. Then call skill("pc-plan-story") and run `/plan-story` for the issue,
310
340
  passing everything you learned while exploring as the exploration findings. Ground the story
311
341
  in the actual codebase by reading the relevant files. Never read outside this repository root.
312
342
  When the issue held several work units, combine them into a single user story that covers all
@@ -323,6 +353,11 @@ timeout-minutes: 40
323
353
 
324
354
  Labels are workflow-owned state. Do not call `add_labels` or `remove_labels`.
325
355
 
356
+ **Do not probe safe-output tools.** Never call `update_issue` or `add_comment` with
357
+ empty or test arguments — each safe-output type has a per-run limit of 1 call, and a
358
+ probe call consumes that quota. Call a safe-output tool exactly once, with the full
359
+ final payload, when you are ready to commit to the outcome.
360
+
326
361
  **Questions remain.** You set aside one or more questions for the author that the codebase
327
362
  could not answer. Leave the body unchanged. Call `add_comment` once with:
328
363
  1. `${{ env.REFINE_MARKER }}`
@@ -351,7 +386,11 @@ flowchart TD
351
386
  refPick -.->|no| refIdle
352
387
  refReserve("Reserve<br/>bot-working") --> refFacts
353
388
  refFacts("Facts<br/>Issue and comments to disk") --> refExplore
354
- refExplore("Explore<br/>ob-plan-explore per work unit,<br/>self-answer, bounded") --> refStory
389
+ refExplore("Explore<br/>pc-plan-explore per work unit,<br/>self-answer, bounded") --> refClassify
390
+ refClassify{"Trivial change?"}
391
+ refClassify -->|yes: trivial path| refTrivial
392
+ refClassify -->|no: standard path| refStory
393
+ refTrivial("Trivial plan<br/>marker + summary + checklist") -->|✓| refProse
355
394
  refStory("Story<br/>/plan-story, grounded in the code") -->|✓| refProse
356
395
  refStory -.->|✗| refFail
357
396
  refProse("Prose<br/>@humanizer over the final text") -->|✓| refOutcome
@@ -371,8 +410,8 @@ flowchart TD
371
410
  classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
372
411
 
373
412
  class refStart start
374
- class refReserve,refFacts,refExplore,refStory,refProse action
375
- class refPick,refOutcome decision
413
+ class refReserve,refFacts,refExplore,refStory,refTrivial,refProse action
414
+ class refPick,refOutcome,refClassify decision
376
415
  class refIdle idle
377
416
  class refFail failure
378
417
  class refDone,refAsk success