@plainconceptsplatform/workflows 1.6.0 → 1.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +0 -0
- package/loops/actions/decide-agent-retry/action.yml +31 -3
- package/loops/actions/decide-agent-retry/classify-failure.sh +50 -0
- package/loops/actions/housekeeping/action.yml +20 -0
- package/loops/actions/identify-gate-subject/action.yml +36 -19
- package/loops/actions/merge-train/action.yml +5 -0
- package/loops/actions/merge-train/merge-train.sh +130 -33
- package/loops/actions/push-agent-branch/action.yml +14 -1
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +177 -5
- package/loops/docs/diagrams.md +2 -2
- package/loops/scripts/compile-agent-workflows.mjs +20 -3
- package/loops/workflows/agent-implement.md +78 -32
- package/loops/workflows/agent-merge-gate.md +19 -9
- package/loops/workflows/agent-refine.md +9 -0
- package/loops/workflows/work-router.yml +54 -7
- package/package.json +9 -8
|
@@ -69,7 +69,7 @@ run-name: >-
|
|
|
69
69
|
&& format('Agentics: Merge Train - CI on PR #{0}', inputs.pr-number)
|
|
70
70
|
|| github.event_name == 'workflow_dispatch'
|
|
71
71
|
&& inputs.operation == 'merge-gate'
|
|
72
|
-
&& format('Agent: Merge Gate - PR #{0}', inputs.pr-number)
|
|
72
|
+
&& format('Agent: Merge Gate - PR #{0}{1}', inputs.pr-number, inputs.ci-conclusion == 'failure' && ' (repair)' || '')
|
|
73
73
|
|| github.event_name == 'workflow_dispatch'
|
|
74
74
|
&& inputs.operation == 'merge-train'
|
|
75
75
|
&& format('Agentics: Merge Train{0}', '')
|
|
@@ -160,6 +160,15 @@ env:
|
|
|
160
160
|
# "true" logs the move the train would make and makes none. For trying the train on a
|
|
161
161
|
# repository before letting it act.
|
|
162
162
|
MERGE_TRAIN_DRY_RUN: "false"
|
|
163
|
+
# How many pull requests the merge train may have in repair at once. A repair moves nothing on
|
|
164
|
+
# the default branch, so repairs of different pull requests do not race; only merges are serial.
|
|
165
|
+
# 1 repairs one pull request at a time.
|
|
166
|
+
MERGE_TRAIN_REPAIRS: "1"
|
|
167
|
+
# How many implement runs may work at once. Each queue is FIFO and serial. An issue joins the
|
|
168
|
+
# queue of its split parent when it has one, so the children of one refine never run side by
|
|
169
|
+
# side on the code they share; independent issues spread across the queues. 1 is one queue
|
|
170
|
+
# for everything, as before this existed. Raise it only with a fleet that has the VMs.
|
|
171
|
+
IMPLEMENT_PARALLELISM: "1"
|
|
163
172
|
# Whether a merged pull request's verification plan is replayed and its screenshots archived on
|
|
164
173
|
# the pull request (evidence.yml). It needs nothing configured: the app starts with the root
|
|
165
174
|
# `pnpm run dev`, and a pull request with no plan costs one API call. Set it to "false" for a
|
|
@@ -522,7 +531,29 @@ jobs:
|
|
|
522
531
|
stack-branch: ${{ steps.stack.outputs.stack-branch }}
|
|
523
532
|
stack-in-flight: ${{ steps.inflight.outputs.stack-in-flight }}
|
|
524
533
|
stack-blocked: ${{ steps.blocked.outputs.stack-blocked }}
|
|
534
|
+
implement-queue: ${{ steps.queue.outputs.queue }}
|
|
525
535
|
steps:
|
|
536
|
+
# Which implement queue this issue waits in. The children of one refine share a queue,
|
|
537
|
+
# keyed on their split parent, because they build on each other's code and two of them at
|
|
538
|
+
# once would each cut from a main the other is about to change. Anything else is keyed on
|
|
539
|
+
# its own number. A body that cannot be read keys on the issue: a wrong queue only costs
|
|
540
|
+
# parallelism, never order within a feature.
|
|
541
|
+
- name: Pick the implement queue
|
|
542
|
+
id: queue
|
|
543
|
+
env:
|
|
544
|
+
GH_TOKEN: ${{ github.token }}
|
|
545
|
+
REPO: ${{ github.repository }}
|
|
546
|
+
ISSUE_NUMBER: ${{ needs.classify.outputs.issue-number }}
|
|
547
|
+
run: |
|
|
548
|
+
set -euo pipefail
|
|
549
|
+
queues="${IMPLEMENT_PARALLELISM:-1}"
|
|
550
|
+
case "$queues" in ''|*[!0-9]*|0) queues=1 ;; esac
|
|
551
|
+
body=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json body --jq '.body // ""' 2>/dev/null || echo '')
|
|
552
|
+
parent=$(printf '%s' "$body" | grep -oE '<!-- split-parent: [0-9]+ -->' | head -1 | grep -oE '[0-9]+' || true)
|
|
553
|
+
key="${parent:-$ISSUE_NUMBER}"
|
|
554
|
+
queue=$(( key % queues ))
|
|
555
|
+
echo "queue=$queue" >> "$GITHUB_OUTPUT"
|
|
556
|
+
echo "#$ISSUE_NUMBER waits in implement queue $queue of $queues (keyed on #$key)."
|
|
526
557
|
- name: Resolve the feature branch from the stack markers
|
|
527
558
|
id: stack
|
|
528
559
|
env:
|
|
@@ -645,8 +676,10 @@ jobs:
|
|
|
645
676
|
needs: [classify, authorize, check-implement-pr, check-stack-branch]
|
|
646
677
|
if: needs.classify.outputs.route == 'implement' && needs.authorize.outputs.trusted == 'true' && needs.check-implement-pr.outputs.has-open-pr != 'true' && needs.check-stack-branch.outputs.is-stack != 'true'
|
|
647
678
|
uses: ./.github/workflows/agent-implement.lock.yml
|
|
679
|
+
# One FIFO queue per IMPLEMENT_PARALLELISM slot, chosen by check-stack-branch: the children of
|
|
680
|
+
# one refine always share a queue, independent issues spread across them.
|
|
648
681
|
concurrency:
|
|
649
|
-
group: implement-
|
|
682
|
+
group: implement-queue-${{ needs.check-stack-branch.outputs.implement-queue || '0' }}
|
|
650
683
|
cancel-in-progress: false
|
|
651
684
|
# FIFO, up to 100 waiting. Without this the default `queue: single` cancels whatever
|
|
652
685
|
# was pending, so labelling six issues at once implements one and drops the rest.
|
|
@@ -793,8 +826,12 @@ jobs:
|
|
|
793
826
|
needs: classify
|
|
794
827
|
if: needs.classify.outputs.route == 'merge-gate'
|
|
795
828
|
uses: ./.github/workflows/agent-merge-gate.lock.yml
|
|
829
|
+
# A gate that was handed red CI repairs and pushes: it can never merge (the validator blocks a
|
|
830
|
+
# non-success conclusion), so it moves nothing on the default branch and cannot race another
|
|
831
|
+
# gate. Those run in a group of their own per pull request, in parallel, while everything that
|
|
832
|
+
# may merge still waits its turn on the one belt.
|
|
796
833
|
concurrency:
|
|
797
|
-
group: merge-belt
|
|
834
|
+
group: ${{ needs.classify.outputs.ci-conclusion == 'failure' && format('merge-repair-{0}', needs.classify.outputs.pr-number) || 'merge-belt' }}
|
|
798
835
|
cancel-in-progress: false
|
|
799
836
|
queue: max
|
|
800
837
|
permissions: write-all
|
|
@@ -865,6 +902,7 @@ jobs:
|
|
|
865
902
|
hold-label: ${{ env.MERGE_TRAIN_HOLD_LABEL }}
|
|
866
903
|
max-gate-attempts: ${{ env.MAX_GATE_ATTEMPTS }}
|
|
867
904
|
max-retries: ${{ env.MERGE_TRAIN_MAX_RETRIES }}
|
|
905
|
+
max-repairs: ${{ env.MERGE_TRAIN_REPAIRS }}
|
|
868
906
|
dry-run: ${{ env.MERGE_TRAIN_DRY_RUN }}
|
|
869
907
|
ci-run-id: ${{ needs.classify.outputs.ci-run-id }}
|
|
870
908
|
ci-conclusion: ${{ needs.classify.outputs.ci-conclusion }}
|
|
@@ -1254,19 +1292,28 @@ jobs:
|
|
|
1254
1292
|
set -euo pipefail
|
|
1255
1293
|
|
|
1256
1294
|
# Stale reservation sweep. Labelling ten issues at once labels all ten immediately,
|
|
1257
|
-
# because authorize-bot-work is keyed per issue, while implement
|
|
1295
|
+
# because authorize-bot-work is keyed per issue, while each implement queue lets exactly
|
|
1258
1296
|
# one run at a time. A queued run that is cancelled therefore leaves its issue
|
|
1259
1297
|
# carrying bot-working with no run and no pull request: parked, invisible, and not
|
|
1260
1298
|
# re-triggerable, because classify refuses an issue that is already reserved.
|
|
1299
|
+
#
|
|
1300
|
+
# The live runs are read by status, not as the repository's latest hundred runs. Every
|
|
1301
|
+
# comment and label is a router run, so a busy repository pushes an implement that has
|
|
1302
|
+
# waited two hours in the queue out of the latest hundred, and the sweep then cleared
|
|
1303
|
+
# bot-working from issues whose runs were alive and about to start (Pliny-Bot #693 and
|
|
1304
|
+
# #707, both still queued from 07:32). Runs that are not finished are few, whatever
|
|
1305
|
+
# their age.
|
|
1306
|
+
active_runs=$(for status in queued in_progress pending waiting requested; do
|
|
1307
|
+
gh api "repos/$REPO/actions/runs?status=$status&per_page=100" --jq '.workflow_runs[]' 2>/dev/null || true
|
|
1308
|
+
done | jq -s '{workflow_runs: .}')
|
|
1261
1309
|
gh issue list --repo "$REPO" --label bot-working --state open --limit 100 \
|
|
1262
1310
|
--json number --jq '.[].number' |
|
|
1263
1311
|
while IFS= read -r issue; do
|
|
1264
1312
|
[ -n "$issue" ] || continue
|
|
1265
1313
|
|
|
1266
|
-
live=$(
|
|
1267
|
-
--jq "[.workflow_runs[]
|
|
1314
|
+
live=$(jq "[.workflow_runs[]
|
|
1268
1315
|
| select(.status == \"queued\" or .status == \"in_progress\" or .status == \"pending\" or .status == \"waiting\")
|
|
1269
|
-
| select((.display_title + \" \") | test(\"#${issue}[^0-9]\"))] | length")
|
|
1316
|
+
| select((.display_title + \" \") | test(\"#${issue}[^0-9]\"))] | length" <<<"$active_runs")
|
|
1270
1317
|
[ "${live:-0}" -eq 0 ] || continue
|
|
1271
1318
|
|
|
1272
1319
|
has_pr=$(gh pr list --repo "$REPO" --state open --json body \
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plainconceptsplatform/workflows",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.11",
|
|
4
4
|
"description": "Install and update Platform GitHub agentic workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
"loops",
|
|
26
26
|
"README.md"
|
|
27
27
|
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc --project tsconfig.build.json && node ./scripts/copy-loops.mjs",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
32
|
+
"release": "pnpm build && pnpm test && npm publish --access public",
|
|
33
|
+
"prepack": "node ./scripts/copy-loops.mjs"
|
|
34
|
+
},
|
|
28
35
|
"engines": {
|
|
29
36
|
"node": ">=20.19"
|
|
30
37
|
},
|
|
@@ -35,11 +42,5 @@
|
|
|
35
42
|
"@types/node": "^22.0.0",
|
|
36
43
|
"typescript": "^5.8.0",
|
|
37
44
|
"vitest": "^3.0.0"
|
|
38
|
-
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "tsc --project tsconfig.build.json && node ./scripts/copy-loops.mjs",
|
|
41
|
-
"test": "vitest run",
|
|
42
|
-
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
43
|
-
"release": "pnpm build && pnpm test && npm publish --access public"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|