@plainconceptsplatform/workflows 0.19.2 → 0.20.0

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