@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.
@@ -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-global
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-global lets exactly
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=$(gh api "repos/$REPO/actions/runs?per_page=100" \
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.0",
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
+ }