@plainconceptsplatform/workflows 1.6.47 → 1.6.48
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/loops/actions/identify-gate-subject/action.yml +7 -1
- package/loops/actions/merge-train/action.yml +5 -0
- package/loops/actions/merge-train/merge-train.sh +58 -1
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +23 -0
- package/loops/workflows/agent-implement.md +10 -0
- package/loops/workflows/work-router.yml +2 -0
- package/package.json +1 -1
|
@@ -144,7 +144,11 @@ runs:
|
|
|
144
144
|
if [ -n "$ci_run_id" ]; then
|
|
145
145
|
run_sha="$(gh api "repos/$REPO/actions/runs/$ci_run_id" --jq '.head_sha' 2>/dev/null || true)"
|
|
146
146
|
current_sha="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')"
|
|
147
|
-
|
|
147
|
+
# A conflicting pull request has no merge ref, so CI never runs on its head: the gate keeps
|
|
148
|
+
# the carried verdict and resolves the conflict. Pliny-Bot #862 was handed to the gate and
|
|
149
|
+
# declined every five minutes for an hour waiting for a run that could not start.
|
|
150
|
+
conflicting="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json mergeable --jq '.mergeable' 2>/dev/null || true)"
|
|
151
|
+
if [ -n "$run_sha" ] && [ "$run_sha" != "$current_sha" ] && [ "$conflicting" != CONFLICTING ]; then
|
|
148
152
|
echo "::notice::CI run $ci_run_id judged ${run_sha:0:7}, but the head is ${current_sha:0:7}; reading the verdict again from the head."
|
|
149
153
|
ci_conclusion=""
|
|
150
154
|
ci_run_id=""
|
|
@@ -154,6 +158,8 @@ runs:
|
|
|
154
158
|
fi
|
|
155
159
|
ci_conclusion="$(jq -r '.conclusion // "pending"' <<<"$fresh")"
|
|
156
160
|
ci_run_id="$(jq -r '.id' <<<"$fresh")"
|
|
161
|
+
elif [ "$conflicting" = CONFLICTING ]; then
|
|
162
|
+
echo "::notice::PR #$PR_NUMBER conflicts with its base, so CI cannot run on its head; the gate resolves the conflict."
|
|
157
163
|
fi
|
|
158
164
|
fi
|
|
159
165
|
|
|
@@ -50,6 +50,10 @@ inputs:
|
|
|
50
50
|
description: Decide and report the move without making it.
|
|
51
51
|
required: false
|
|
52
52
|
default: 'false'
|
|
53
|
+
ci-depth-tiered:
|
|
54
|
+
description: "true when the repository's CI runs only light checks on a bot's push and the full CI when the train asks (repository variable CI_DEPTH_TIERED)."
|
|
55
|
+
required: false
|
|
56
|
+
default: 'false'
|
|
53
57
|
ci-run-id:
|
|
54
58
|
description: A CI run that handed its verdict over from inside itself, and so still reads as in progress.
|
|
55
59
|
required: false
|
|
@@ -78,6 +82,7 @@ runs:
|
|
|
78
82
|
MAIN_RED_WAIT_HOURS: ${{ inputs.main-red-wait-hours }}
|
|
79
83
|
MAX_REPAIRS: ${{ inputs.max-repairs }}
|
|
80
84
|
DRY_RUN: ${{ inputs.dry-run }}
|
|
85
|
+
CI_DEPTH_TIERED: ${{ inputs.ci-depth-tiered }}
|
|
81
86
|
HINT_RUN_ID: ${{ inputs.ci-run-id }}
|
|
82
87
|
HINT_CONCLUSION: ${{ inputs.ci-conclusion }}
|
|
83
88
|
run: bash "${GITHUB_ACTION_PATH}/merge-train.sh"
|
|
@@ -70,6 +70,8 @@ train_move() {
|
|
|
70
70
|
success)
|
|
71
71
|
if [ "${BEHIND_BY:-0}" -gt 0 ]; then
|
|
72
72
|
move=update; reason="green but ${BEHIND_BY} commit(s) behind its base branch"
|
|
73
|
+
elif [ "${CI_SHALLOW:-false}" = true ]; then
|
|
74
|
+
move=deepen; reason="green on the light checks only; the full CI has not run on this head"
|
|
73
75
|
else
|
|
74
76
|
move=gate; reason="green and up to date"
|
|
75
77
|
fi
|
|
@@ -199,6 +201,32 @@ act() {
|
|
|
199
201
|
"$@"
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
# Tiered CI: the label that asks a pull request's CI for the full depth. The CI's depth job reads the
|
|
205
|
+
# pull request's labels at run time, so a re-run of the same pull_request run turns full: the event and
|
|
206
|
+
# its context stay the pull request's, which the scope, mutation and coverage jobs rely on (a separate
|
|
207
|
+
# workflow_dispatch run would lose it, and mutation runs on pull_request events only).
|
|
208
|
+
CI_FULL_LABEL="ci-full"
|
|
209
|
+
|
|
210
|
+
mark_full_ci() {
|
|
211
|
+
local pr="$1"
|
|
212
|
+
gh label create "$CI_FULL_LABEL" --repo "$REPO" --color 0E8A16 \
|
|
213
|
+
--description "The CI runs at full depth on this pull request (set by the merge train)" >/dev/null 2>&1 || true
|
|
214
|
+
gh api -X POST "repos/$REPO/issues/$pr/labels" -f "labels[]=$CI_FULL_LABEL" >/dev/null 2>&1
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
deepen_ci() {
|
|
218
|
+
local pr="$1" run="$2"
|
|
219
|
+
[ -n "$run" ] || return 1
|
|
220
|
+
mark_full_ci "$pr" || return 1
|
|
221
|
+
# A CI handover reaches the train from inside the run, and a run still going cannot be re-run.
|
|
222
|
+
local i
|
|
223
|
+
for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
|
|
224
|
+
[ "$(gh api "repos/$REPO/actions/runs/$run" --jq .status 2>/dev/null)" = completed ] && break
|
|
225
|
+
sleep 10
|
|
226
|
+
done
|
|
227
|
+
gh run rerun "$run" --repo "$REPO" >/dev/null 2>&1
|
|
228
|
+
}
|
|
229
|
+
|
|
202
230
|
is_trusted_bot() {
|
|
203
231
|
case " $TRUSTED_BOTS " in *" $1 "*) return 0 ;; esac
|
|
204
232
|
return 1
|
|
@@ -435,6 +463,16 @@ for line in "${candidates[@]}"; do
|
|
|
435
463
|
success | failure) ci_status=completed; ci_conclusion="$HINT_CONCLUSION" ;;
|
|
436
464
|
esac
|
|
437
465
|
fi
|
|
466
|
+
# Tiered CI (CI_DEPTH_TIERED): a bot's push runs the light checks and the full CI runs once, when the
|
|
467
|
+
# pull request is next in line. Pliny-Bot spent two thirds of its pull request CI on heads that were
|
|
468
|
+
# replaced before they merged. A green run without a successful "Full CI" job was light and says
|
|
469
|
+
# nothing about the tests. When the jobs cannot be read the run is taken as full, as before.
|
|
470
|
+
ci_shallow=false
|
|
471
|
+
if [ "${CI_DEPTH_TIERED:-false}" = true ] && [ "$ci_status" = completed ] && [ "$ci_conclusion" = success ] && [ -n "${ci_run_id:-}" ]; then
|
|
472
|
+
full_jobs=$(gh api "repos/$REPO/actions/runs/$ci_run_id/jobs?per_page=100" \
|
|
473
|
+
--jq '[.jobs[] | select((.name | test("(^|/ )Full CI$")) and .conclusion == "success")] | length' 2>/dev/null || echo "")
|
|
474
|
+
[ "$full_jobs" = 0 ] && ci_shallow=true
|
|
475
|
+
fi
|
|
438
476
|
ci_stuck=false
|
|
439
477
|
if [ "$ci_status" != none ] && [ "$ci_status" != completed ] && [ -n "${ci_created:-}" ]; then
|
|
440
478
|
[ $((now_epoch - $(iso_epoch "$ci_created"))) -lt $((STUCK_HOURS * 3600)) ] || ci_stuck=true
|
|
@@ -503,7 +541,7 @@ for line in "${candidates[@]}"; do
|
|
|
503
541
|
decision=$(HOLD="$hold" REVIEW_DECISION="$review_decision" PARK="$park" \
|
|
504
542
|
ATTEMPTS="$attempts" MAX_ATTEMPTS="$MAX_ATTEMPTS" SILENT_GATE="$silent_gate" MERGEABLE="$mergeable" \
|
|
505
543
|
CI_STATUS="$ci_status" CI_CONCLUSION="$ci_conclusion" CI_STUCK="$ci_stuck" SAME_AS_MAIN="$same_as_main" \
|
|
506
|
-
BEHIND_BY="${behind_by:-0}" TRUSTED_AUTHOR="$trusted_author" train_move)
|
|
544
|
+
BEHIND_BY="${behind_by:-0}" TRUSTED_AUTHOR="$trusted_author" CI_SHALLOW="$ci_shallow" train_move)
|
|
507
545
|
move=$(sed -n 's/^move=//p' <<<"$decision")
|
|
508
546
|
reason=$(sed -n 's/^reason=//p' <<<"$decision")
|
|
509
547
|
|
|
@@ -544,11 +582,30 @@ for line in "${candidates[@]}"; do
|
|
|
544
582
|
merge_blocked=true
|
|
545
583
|
continue
|
|
546
584
|
;;
|
|
585
|
+
deepen)
|
|
586
|
+
if [ "$merge_blocked" = true ]; then
|
|
587
|
+
echo " #$pr waits for the pull request ahead of it before its full CI runs."
|
|
588
|
+
continue
|
|
589
|
+
fi
|
|
590
|
+
if act "Running the full CI on #$pr, which is next in line." deepen_ci "$pr" "${ci_run_id:-}"; then
|
|
591
|
+
merge_blocked=true
|
|
592
|
+
continue
|
|
593
|
+
fi
|
|
594
|
+
# Never a gate on the light run: it could merge code no test has run on. The pull request is
|
|
595
|
+
# skipped this tick and the line moves on, so one that cannot be deepened does not stop the rest.
|
|
596
|
+
echo "::warning::Could not start the full CI on #$pr; it is skipped this tick."
|
|
597
|
+
continue
|
|
598
|
+
;;
|
|
547
599
|
update)
|
|
548
600
|
if [ "$merge_blocked" = true ]; then
|
|
549
601
|
echo " #$pr waits for the pull request ahead of it before it is brought up to date."
|
|
550
602
|
continue
|
|
551
603
|
fi
|
|
604
|
+
# Next in line: with tiered CI the run this update starts is the one that decides, so it runs full.
|
|
605
|
+
if [ "${CI_DEPTH_TIERED:-false}" = true ]; then
|
|
606
|
+
act "Asking for the full CI on #$pr before it is brought up to date." mark_full_ci "$pr" ||
|
|
607
|
+
echo "::warning::Could not label #$pr for the full CI; its update runs the light checks."
|
|
608
|
+
fi
|
|
552
609
|
# Merge, never rebase: the gate's pushes are fast-forward only, and so are a person's.
|
|
553
610
|
if act "Merging $base_ref into #$pr so CI judges it against what it will land on." \
|
|
554
611
|
update_branch "$pr" "$head_sha"; then
|
|
@@ -2863,6 +2863,29 @@ else
|
|
|
2863
2863
|
FAIL=$((FAIL + 1))
|
|
2864
2864
|
echo "FAIL: the gate measures a stack child's comments and tests against the default branch" >&2
|
|
2865
2865
|
fi
|
|
2866
|
+
# Tiered CI: the train deepens a light green run instead of gating it, and never gates a light run.
|
|
2867
|
+
if grep -qF 'move=deepen' "${HERE}/../merge-train/merge-train.sh" &&
|
|
2868
|
+
grep -qF 'deepen_ci "$pr" "${ci_run_id:-}"' "${HERE}/../merge-train/merge-train.sh" &&
|
|
2869
|
+
grep -qF "ci-depth-tiered: \${{ vars.CI_DEPTH_TIERED || 'false' }}" "${WORKFLOWS_DIR}/work-router.yml"; then
|
|
2870
|
+
PASS=$((PASS + 1))
|
|
2871
|
+
else
|
|
2872
|
+
FAIL=$((FAIL + 1))
|
|
2873
|
+
echo "FAIL: the merge train does not ask for the full CI before gating a light run" >&2
|
|
2874
|
+
fi
|
|
2875
|
+
# The implement agent tests only what it changed; the repository's CI runs the full suite.
|
|
2876
|
+
if grep -qF 'Test only what you changed; CI tests the rest.' "${WORKFLOWS_DIR}/agent-implement.md"; then
|
|
2877
|
+
PASS=$((PASS + 1))
|
|
2878
|
+
else
|
|
2879
|
+
FAIL=$((FAIL + 1))
|
|
2880
|
+
echo "FAIL: implement tests only what it changed is missing from the prompt" >&2
|
|
2881
|
+
fi
|
|
2882
|
+
# A conflicting pull request is gated on its carried verdict: CI cannot run on a head with no merge ref.
|
|
2883
|
+
if grep -qF '[ "$conflicting" != CONFLICTING ]' "${HERE}/../identify-gate-subject/action.yml"; then
|
|
2884
|
+
PASS=$((PASS + 1))
|
|
2885
|
+
else
|
|
2886
|
+
FAIL=$((FAIL + 1))
|
|
2887
|
+
echo "FAIL: the gate waits for CI on a conflicting pull request, which never gets a run" >&2
|
|
2888
|
+
fi
|
|
2866
2889
|
# An empty AGENT_TIER_MODEL must remove an override a reused runner kept from an earlier job.
|
|
2867
2890
|
if grep -qF 'rm -f .opencode/harness.user.json' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
|
|
2868
2891
|
PASS=$((PASS + 1))
|
|
@@ -839,6 +839,16 @@ timeout-minutes: 180
|
|
|
839
839
|
|
|
840
840
|
Never open a pull request that does not pass them.
|
|
841
841
|
|
|
842
|
+
**Test only what you changed; CI tests the rest.** In this run, run the tests that cover the
|
|
843
|
+
code you touched and nothing wider: the test files you added or edited, and the tests next to
|
|
844
|
+
the code you changed (web: `pnpm exec vitest related --run <changed files>`; .NET:
|
|
845
|
+
`dotnet test <the test project> --filter <the test classes>`). Do not run integration,
|
|
846
|
+
end-to-end, mutation or coverage suites here, and do not run the whole test suite to be safe:
|
|
847
|
+
the repository's CI runs all of it on your pull request before anything merges, on a runner
|
|
848
|
+
that has the database and the browsers this sandbox lacks. The verification commands above are
|
|
849
|
+
the repository's quick check for that reason. A full suite in the agent is the slowest part of
|
|
850
|
+
a run and repeats what CI does anyway.
|
|
851
|
+
|
|
842
852
|
**Run them once, on the final tree.** They are slow: Pliny-Bot's `pnpm verify` is about
|
|
843
853
|
twenty minutes of work on this runner. `pc-plan-apply` runs them in its verify step, and
|
|
844
854
|
that run is this one: do not run them again when nothing changed since. Order the end of
|
|
@@ -938,6 +938,8 @@ jobs:
|
|
|
938
938
|
dry-run: ${{ env.MERGE_TRAIN_DRY_RUN }}
|
|
939
939
|
ci-run-id: ${{ needs.classify.outputs.ci-run-id }}
|
|
940
940
|
ci-conclusion: ${{ needs.classify.outputs.ci-conclusion }}
|
|
941
|
+
# Repository variable, read by the repository's CI as well: one switch for both sides.
|
|
942
|
+
ci-depth-tiered: ${{ vars.CI_DEPTH_TIERED || 'false' }}
|
|
941
943
|
|
|
942
944
|
# The decision the gate parked for: a person with write access approves a pull request the
|
|
943
945
|
# gate handed to them (human-review or owner-review), and the bot merges the commit they
|