@plot-pm/board 0.14.4 → 0.15.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.
- package/dist/board-server.mjs +164 -158
- package/package.json +2 -1
- package/plot-agent-manifest.sh +58 -0
- package/plot-approve.sh +23 -1
- package/plot-config.sh +34 -1
- package/plot-deliver.sh +24 -3
- package/plot-dispatch.sh +283 -13
- package/plot-fleet-scan.sh +387 -79
- package/plot-host.sh +95 -12
- package/plot-plan-meta.sh +10 -3
- package/plot-pr-merged.sh +29 -2
- package/plot-reap.sh +115 -22
package/plot-fleet-scan.sh
CHANGED
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
# --list-eligible print EVERY claimable branch, one per line (exit 1 if none).
|
|
17
17
|
# For callers that need the count rather than one item — a dry
|
|
18
18
|
# run changes nothing, so its answer cannot go stale.
|
|
19
|
+
# WHEN THERE ARE NONE IT SAYS WHY, ON STDERR: whether every
|
|
20
|
+
# candidate in an eligible wave is taken, or no eligible wave held
|
|
21
|
+
# a startable branch at all. Two silences a reader cannot
|
|
22
|
+
# otherwise tell apart (#994). Stdout stays a bare branch list
|
|
23
|
+
# because `plot-dispatch.sh` pipes it through `sort -u` and
|
|
24
|
+
# dispatches every line, and the exit code stays 1 either way, so
|
|
25
|
+
# no caller's gate moves. An estate with NO plans exits earlier
|
|
26
|
+
# and says nothing — there is no candidate set to describe.
|
|
19
27
|
# --loose a prior wave counts as satisfied when its branches carry PUSHED
|
|
20
28
|
# work, not only merged work. Buys throughput, pays in rebase
|
|
21
29
|
# risk — the plan requires a stated reason for using it. Default
|
|
@@ -88,12 +96,21 @@
|
|
|
88
96
|
# evidence of a typo either.
|
|
89
97
|
# Output: per-plan wave report on stdout, terminated by a machine-countable
|
|
90
98
|
# summary line:
|
|
91
|
-
# summary: plans=1 waves=3 branches=5 claimed=1 eligible=2 blocked=1 deferred=1 waiting=1 prereq_missing=0 merge_detect=pr-merge host=ok main=main
|
|
99
|
+
# summary: plans=1 waves=3 branches=5 claimed=1 claimable=2 eligible=2 blocked=1 deferred=1 waiting=1 prereq_missing=0 merge_detect=pr-merge host=ok main=main
|
|
92
100
|
# `host` is one of ok, partial, throttled, secondary, failed, unasked —
|
|
93
101
|
# `partial` means some of the host's states answered and some did not,
|
|
94
102
|
# so the PR readings below are incomplete rather than absent.
|
|
95
103
|
# `blocked` counts WAVES an earlier wave holds; `waiting` and
|
|
96
104
|
# `prereq_missing` count BRANCHES their `waits:` annotation holds.
|
|
105
|
+
# `claimable` counts BRANCHES a worker may take now, and it is the key
|
|
106
|
+
# the offer paths agree with — `--next` names one of them and
|
|
107
|
+
# `--list-eligible` names all of them. `eligible=` carries the SAME
|
|
108
|
+
# number beside it and is kept for consumers that read it; it was the
|
|
109
|
+
# only name this count had until 2026-09-25, sitting between two wave
|
|
110
|
+
# counts while counting branches, so a wave printed `eligible` in the
|
|
111
|
+
# body while the footer read `eligible=0` and both were right about
|
|
112
|
+
# different questions (#994). A reader wanting the WAVE answer counts
|
|
113
|
+
# the body's verdicts; no footer key reports it.
|
|
97
114
|
# merge_detect names how merged-and-deleted branches were detected:
|
|
98
115
|
# pr-merge (exhaustive), truncated (capped walk), none (no conforming
|
|
99
116
|
# merge commits — a squash/rebase repo, where `open` says nothing about
|
|
@@ -119,10 +136,18 @@
|
|
|
119
136
|
# The plan set also includes plans delivered inside a rolling 24 h
|
|
120
137
|
# window (see "the last day of finished work"), so work does not
|
|
121
138
|
# disappear at the moment it becomes finished.
|
|
122
|
-
# Plans are enumerated from
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
139
|
+
# Plans are enumerated from REFS (`git ls-tree`/`git show`), NOT from
|
|
140
|
+
# the working tree — so the list describes committed state and does not
|
|
141
|
+
# change while rebases and worker commits rewrite the checkout
|
|
142
|
+
# underneath a running fleet. `origin/<main>` first, then each prefixed
|
|
143
|
+
# branch's own tree for the plans the default branch does not carry:
|
|
144
|
+
# a plan created with `Impl: same branch` lives only on its work branch
|
|
145
|
+
# until that branch merges, and reading main alone reported its branch as
|
|
146
|
+
# an anonymous row with `plan: ""` while the Board tab showed the same
|
|
147
|
+
# plan as a Draft card (#972). A plan path is taken ONCE — the default
|
|
148
|
+
# branch wins, and among branches the first to carry it — because two
|
|
149
|
+
# branches cut from one point hold the same file and a row per branch
|
|
150
|
+
# would report one plan as several. A consequence worth stating: an
|
|
126
151
|
# UNCOMMITTED plan is invisible, deliberately — the fleet view shows
|
|
127
152
|
# what is shared, and a plan only this machine has cannot be claimed by
|
|
128
153
|
# any worker. --json carries `plan_source` (ref | worktree), which
|
|
@@ -697,49 +722,11 @@ REMOTE_REFS=$(git for-each-ref --format='%(refname:strip=3)%09%(objectname)' \
|
|
|
697
722
|
# falls back to the listing there, which is what it has always done.
|
|
698
723
|
TRACKED_BRANCHES=$(printf '%s\n' "$REMOTE_REFS" | cut -f1 | grep -v '^HEAD$' | grep -v '^$' | tr '\n' ' ')
|
|
699
724
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
# answer. A failed list leaves the cache EMPTY, so every branch falls through
|
|
706
|
-
# to the unanswerable `-` rather than to a fabricated "no PR".
|
|
707
|
-
#
|
|
708
|
-
# `--rich` adds `checks` and `draft` to the response — the fields `--loose`
|
|
709
|
-
# needs to verify that a prior wave's PR is actually green and ready.
|
|
710
|
-
# Requested unconditionally because the cost is zero on GitHub (same GraphQL
|
|
711
|
-
# call) and bounded on Bitbucket (N extra calls only when CI: jenkins is
|
|
712
|
-
# configured), and the parsing below already skips fields the response does
|
|
713
|
-
# not contain. The BEHAVIOUR change is in `pr_ready`, which now reads the
|
|
714
|
-
# check rollup from the cache rather than making a per-branch host call.
|
|
715
|
-
#
|
|
716
|
-
# THE CODE IS KEPT, not just tested. This guard was always correct — a failed
|
|
717
|
-
# list prefills nothing — and until 2026-08-30 it never fired, because
|
|
718
|
-
# `pr-list` swallowed its own failure and exited 0 with empty stdout. Now
|
|
719
|
-
# that it can fail, WHICH failure it was is a fact worth carrying: exit 5 is
|
|
720
|
-
# a rate limit and exit 3 is anything else, and the summary reports the
|
|
721
|
-
# difference rather than degrading silently.
|
|
722
|
-
# STDOUT TO A FILE so stderr can be captured separately — see the verdict
|
|
723
|
-
# below. It lives in `HOST_STATE_CACHE`, which already has an EXIT trap, so
|
|
724
|
-
# this adds no second cleanup path. When mktemp -d failed the cache is "",
|
|
725
|
-
# and /dev/null keeps the call working with the text simply unavailable.
|
|
726
|
-
host_list_out="${HOST_STATE_CACHE:+$HOST_STATE_CACHE/pr-list.json}"
|
|
727
|
-
host_list_out="${host_list_out:-/dev/null}"
|
|
728
|
-
# THE BRANCHES THIS SCAN TRACKS, HANDED TO THE HOST (#333). The adapter uses
|
|
729
|
-
# them only where it can — the Bitbucket arm sweeps its REST endpoint once per
|
|
730
|
-
# branch per state — and ignores them everywhere else, so the GitHub arm makes
|
|
731
|
-
# the single call it always made. Passing them unconditionally keeps one call
|
|
732
|
-
# shape here rather than a backend test this script has no business making.
|
|
733
|
-
#
|
|
734
|
-
# AN EMPTY SET PASSES NOTHING and the adapter lists as before. See
|
|
735
|
-
# `TRACKED_BRANCHES`: a completeness claim over an empty set would license
|
|
736
|
-
# `NONE` for branches nobody asked about.
|
|
737
|
-
_branch_args=()
|
|
738
|
-
for _tb in $TRACKED_BRANCHES; do _branch_args+=(--branch "$_tb"); done
|
|
739
|
-
host_err=$("$script_dir/plot-host.sh" pr-list --state all --limit "$PR_LIST_LIMIT" --rich \
|
|
740
|
-
${_branch_args[@]+"${_branch_args[@]}"} \
|
|
741
|
-
</dev/null 2>&1 >"$host_list_out"); rc=$?
|
|
742
|
-
js=$(cat "$host_list_out" 2>/dev/null)
|
|
725
|
+
# ONE `pr-list` CALL'S EXIT, READ AS A VERDICT WORD in `_plv` — a global
|
|
726
|
+
# rather than stdout, so the two calls in `prefill_pr_states` fork nothing to
|
|
727
|
+
# classify themselves. `$1` is the exit code, `$2` the call's stderr.
|
|
728
|
+
pr_list_verdict() {
|
|
729
|
+
local rc="$1" err="$2"
|
|
743
730
|
# A PARTIAL ANSWER TAKES THE PARSE PATH AND STILL DEGRADES THE VERDICT, which
|
|
744
731
|
# is a control-flow change rather than another `case` arm below: every other
|
|
745
732
|
# non-zero rc sets a verdict and returns BEFORE `$js` is read, because there
|
|
@@ -750,9 +737,11 @@ prefill_pr_states() {
|
|
|
750
737
|
# BOTH HALVES ARE REQUIRED. Falling through without setting the verdict would
|
|
751
738
|
# report `ok` over a page missing a whole state, which is #912; returning
|
|
752
739
|
# early would throw away rows the host did answer with.
|
|
753
|
-
if [ "$rc" -eq
|
|
754
|
-
|
|
755
|
-
elif [ "$rc" -
|
|
740
|
+
if [ "$rc" -eq 0 ]; then
|
|
741
|
+
_plv=ok
|
|
742
|
+
elif [ "$rc" -eq 7 ]; then
|
|
743
|
+
_plv=partial
|
|
744
|
+
else
|
|
756
745
|
# THREE OUTCOMES, NOT TWO. `unasked` already means "the question was
|
|
757
746
|
# never put" (see HOST_VERDICT above: *not a degradation, the scan was
|
|
758
747
|
# never asking*), and a host that cannot be ASKED AT ALL belongs there
|
|
@@ -774,10 +763,10 @@ prefill_pr_states() {
|
|
|
774
763
|
# always read 1. The branch was startable; the scan had stopped being
|
|
775
764
|
# able to say so.
|
|
776
765
|
case "$rc" in
|
|
777
|
-
5)
|
|
778
|
-
6)
|
|
779
|
-
4)
|
|
780
|
-
*) case "$
|
|
766
|
+
5) _plv=throttled ;;
|
|
767
|
+
6) _plv=secondary ;;
|
|
768
|
+
4) _plv=unasked ;;
|
|
769
|
+
*) case "$err" in
|
|
781
770
|
# THE WORDING IS MEASURED, NOT GUESSED. An earlier version of this
|
|
782
771
|
# list matched auth/login/credential and MISSED the message CI
|
|
783
772
|
# actually emits:
|
|
@@ -794,7 +783,7 @@ prefill_pr_states() {
|
|
|
794
783
|
# a real failure and stays `failed`, because widening this to a
|
|
795
784
|
# catch-all would turn every host outage into "nobody asked".
|
|
796
785
|
*TOKEN*|*token*|*auth*|*Auth*|*AUTH*|*login*|*Login*|*credential*|*Credential*|*"not logged"*)
|
|
797
|
-
|
|
786
|
+
_plv=unasked ;;
|
|
798
787
|
# NO REMOTE IS A CONFIGURATION, NOT A FAULT — the same reading as a
|
|
799
788
|
# missing token one line up, reached by the same route: `plot-host.sh`
|
|
800
789
|
# exits 3 for both, because both are "the op cannot proceed", and
|
|
@@ -813,19 +802,123 @@ prefill_pr_states() {
|
|
|
813
802
|
# claimable — the right refusal about the wrong thing. There is no
|
|
814
803
|
# merge state to withhold on where there is no remote to hold it.
|
|
815
804
|
*"no git remotes"*|*"no remote"*)
|
|
816
|
-
|
|
817
|
-
*)
|
|
805
|
+
_plv=unasked ;;
|
|
806
|
+
*) _plv=failed ;;
|
|
818
807
|
esac ;;
|
|
819
808
|
esac
|
|
820
|
-
return 0
|
|
821
809
|
fi
|
|
822
|
-
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
# WHICH OF TWO VERDICTS IS WORSE. Every word that returns before a payload is
|
|
813
|
+
# read outranks both words that parse one, so a failed call can never be
|
|
814
|
+
# reported as `ok` or `partial` beside a call that answered.
|
|
815
|
+
# The rank is set in `_plr`, for the same no-fork reason as `_plv`.
|
|
816
|
+
pr_list_verdict_rank() {
|
|
817
|
+
case "$1" in
|
|
818
|
+
ok) _plr=0 ;; partial) _plr=1 ;; unasked) _plr=2 ;;
|
|
819
|
+
secondary) _plr=3 ;; throttled) _plr=4 ;; *) _plr=5 ;;
|
|
820
|
+
esac
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
prefill_pr_states() {
|
|
824
|
+
[ "$HOST_LOOKUP_OK" = 1 ] || return 0
|
|
825
|
+
[ -n "$HOST_STATE_CACHE" ] || return 0
|
|
826
|
+
local js br st key rc
|
|
827
|
+
# Exit code first: non-zero is a transport failure and its stdout is not an
|
|
828
|
+
# answer. A failed list leaves the cache EMPTY, so every branch falls through
|
|
829
|
+
# to the unanswerable `-` rather than to a fabricated "no PR".
|
|
830
|
+
#
|
|
831
|
+
# `--rich` adds `checks` and `draft` to the response — the fields `--loose`
|
|
832
|
+
# needs to verify that a prior wave's PR is actually green and ready. The
|
|
833
|
+
# BEHAVIOUR change is in `pr_ready`, which reads the check rollup from the
|
|
834
|
+
# cache rather than making a per-branch host call.
|
|
835
|
+
#
|
|
836
|
+
# THE ROLLUP IS ASKED OF OPEN PRS ONLY. On GitHub it is `statusCheckRollup`,
|
|
837
|
+
# and its cost scales with the rows it is asked for. Measured 2026-09-25 on
|
|
838
|
+
# this repo through `plot-host.sh`: 957 PRs (920 merged, 34 closed, 3 open),
|
|
839
|
+
# and the one `--state all --rich` call took ~37 s of a ~55 s scan. A merged
|
|
840
|
+
# or closed PR's checks cannot change, and `pr_ready` reads them only for an
|
|
841
|
+
# open one. So TWO calls: `--state open --rich` for the PRs whose checks can
|
|
842
|
+
# still move, and `--state all` without `--rich` for every PR's state.
|
|
823
843
|
#
|
|
824
|
-
#
|
|
825
|
-
#
|
|
826
|
-
#
|
|
827
|
-
#
|
|
828
|
-
|
|
844
|
+
# THE CODE IS KEPT, not just tested. This guard was always correct — a failed
|
|
845
|
+
# list prefills nothing — and until 2026-08-30 it never fired, because
|
|
846
|
+
# `pr-list` swallowed its own failure and exited 0 with empty stdout. Now
|
|
847
|
+
# that it can fail, WHICH failure it was is a fact worth carrying: exit 5 is
|
|
848
|
+
# a rate limit and exit 3 is anything else, and the summary reports the
|
|
849
|
+
# difference rather than degrading silently.
|
|
850
|
+
# STDOUT TO A FILE so stderr can be captured separately — see the verdict
|
|
851
|
+
# below. It lives in `HOST_STATE_CACHE`, which already has an EXIT trap, so
|
|
852
|
+
# this adds no second cleanup path. When mktemp -d failed the cache is "",
|
|
853
|
+
# and /dev/null keeps the call working with the text simply unavailable.
|
|
854
|
+
local open_list_out host_list_out open_err rc_open
|
|
855
|
+
open_list_out="${HOST_STATE_CACHE:+$HOST_STATE_CACHE/pr-list-open.json}"
|
|
856
|
+
open_list_out="${open_list_out:-/dev/null}"
|
|
857
|
+
host_list_out="${HOST_STATE_CACHE:+$HOST_STATE_CACHE/pr-list.json}"
|
|
858
|
+
host_list_out="${host_list_out:-/dev/null}"
|
|
859
|
+
# THE BRANCHES THIS SCAN TRACKS, HANDED TO THE HOST (#333). The adapter uses
|
|
860
|
+
# them only where it can — the Bitbucket arm sweeps its REST endpoint once per
|
|
861
|
+
# branch per state — and ignores them everywhere else, so the GitHub arm makes
|
|
862
|
+
# the single call it always made. Passing them unconditionally keeps one call
|
|
863
|
+
# shape here rather than a backend test this script has no business making.
|
|
864
|
+
#
|
|
865
|
+
# AN EMPTY SET PASSES NOTHING and the adapter lists as before. See
|
|
866
|
+
# `TRACKED_BRANCHES`: a completeness claim over an empty set would license
|
|
867
|
+
# `NONE` for branches nobody asked about.
|
|
868
|
+
_branch_args=()
|
|
869
|
+
for _tb in $TRACKED_BRANCHES; do _branch_args+=(--branch "$_tb"); done
|
|
870
|
+
# BOTH CALLS TAKE `--limit` AND THE BRANCHES. Without `--limit` the host
|
|
871
|
+
# returns 30 (see `PR_LIST_LIMIT`). The branches make the Bitbucket arm sweep
|
|
872
|
+
# the open state exactly rather than list a fixed 50 of it: an open PR
|
|
873
|
+
# missing from the rich payload has NO row at all once the plain payload's
|
|
874
|
+
# OPEN rows are dropped below, and a missing row reads as "no PR".
|
|
875
|
+
#
|
|
876
|
+
# OPEN FIRST, THEN ALL. A PR that merges between the two calls then carries
|
|
877
|
+
# a rich OPEN row and a plain MERGED one, and the rank keeps OPEN — stale by
|
|
878
|
+
# seconds, and the next scan corrects it.
|
|
879
|
+
open_err=$("$script_dir/plot-host.sh" pr-list --state open --limit "$PR_LIST_LIMIT" --rich \
|
|
880
|
+
${_branch_args[@]+"${_branch_args[@]}"} \
|
|
881
|
+
</dev/null 2>&1 >"$open_list_out"); rc_open=$?
|
|
882
|
+
# THE VERDICT IS THE WORSE OF THE TWO, never the last one. A rich call
|
|
883
|
+
# throttled while the plain one answers leaves `checks` absent for every open
|
|
884
|
+
# PR, so `--loose` degrades to strict — and a footer reading `host=ok` would
|
|
885
|
+
# give no reason for it.
|
|
886
|
+
#
|
|
887
|
+
# A FAILED CALL PREFILLS NOTHING, whichever of the two failed. That is the
|
|
888
|
+
# single call's behaviour carried over: a verdict other than `ok` or
|
|
889
|
+
# `partial` sets the verdict and returns before any payload is read. A
|
|
890
|
+
# failed `open` call therefore decides the outcome alone, and the `all` call
|
|
891
|
+
# is not made: its answer could not change the verdict's direction, and on a
|
|
892
|
+
# throttled host it would spend quota on a payload nothing reads.
|
|
893
|
+
pr_list_verdict "$rc_open" "$open_err"; _v_open=$_plv
|
|
894
|
+
case "$_v_open" in ok|partial) ;; *) HOST_VERDICT=$_v_open; return 0 ;; esac
|
|
895
|
+
host_err=$("$script_dir/plot-host.sh" pr-list --state all --limit "$PR_LIST_LIMIT" \
|
|
896
|
+
${_branch_args[@]+"${_branch_args[@]}"} \
|
|
897
|
+
</dev/null 2>&1 >"$host_list_out"); rc=$?
|
|
898
|
+
pr_list_verdict "$rc" "$host_err"; _v_all=$_plv
|
|
899
|
+
pr_list_verdict_rank "$_v_open"; _r_open=$_plr
|
|
900
|
+
pr_list_verdict_rank "$_v_all"
|
|
901
|
+
if [ "$_r_open" -gt "$_plr" ]; then HOST_VERDICT=$_v_open; else HOST_VERDICT=$_v_all; fi
|
|
902
|
+
case "$HOST_VERDICT" in ok|partial) ;; *) return 0 ;; esac
|
|
903
|
+
# THE MERGE, AND THE TRAP IT AVOIDS. An open PR is in BOTH payloads — rich
|
|
904
|
+
# from the `open` call, plain from the `all` call — and both rows rank 1 in
|
|
905
|
+
# the dedup below. With the sort key exhausted, `sort` compares whole lines,
|
|
906
|
+
# and the plain row's `-` sentinel (0x2D) sorts before any letter: the plain
|
|
907
|
+
# row wins every time and the rollup is lost for 100% of open PRs.
|
|
908
|
+
#
|
|
909
|
+
# So the `all` payload's OPEN rows are dropped before the concatenation, and
|
|
910
|
+
# each branch contributes one OPEN row at most. Those rows are unused: the
|
|
911
|
+
# `open` call already answered for every open PR, with its rollup.
|
|
912
|
+
#
|
|
913
|
+
# THE COMPLETENESS COUNT IS TAKEN FROM THE `all` PAYLOAD BEFORE THE FILTER.
|
|
914
|
+
# `.list-complete` compares a row count against `PR_LIST_LIMIT`, and a count
|
|
915
|
+
# taken after the filter can fall below the limit for a list that reached it
|
|
916
|
+
# — a truncated list then reads as whole, and a cache miss derives `NONE` for
|
|
917
|
+
# a branch that has a PR. The `open` payload is counted too, since it is a
|
|
918
|
+
# page of its own.
|
|
919
|
+
_pr_rows=$(grep -c '"state":"[A-Z]*","head":"' "$host_list_out" 2>/dev/null) || _pr_rows=0
|
|
920
|
+
_pr_open_rows=$(grep -c '"state":"[A-Z]*","head":"' "$open_list_out" 2>/dev/null) || _pr_open_rows=0
|
|
921
|
+
js=$(cat "$open_list_out" 2>/dev/null; grep -v '"state":"OPEN","head":"' "$host_list_out" 2>/dev/null)
|
|
829
922
|
# `pr-list` emits one compact JSON object per line. PARSED IN ONE PASS, and
|
|
830
923
|
# that is a correctness-of-cost property rather than a style preference:
|
|
831
924
|
# measured 2026-08-18 on this repo's 221 PRs, a `sed` per field per row —
|
|
@@ -860,12 +953,8 @@ prefill_pr_states() {
|
|
|
860
953
|
# response, which is now only an error case) reads as empty and `pr_ready`
|
|
861
954
|
# treats it as `unknown`, which degrades to strict — the safer direction.
|
|
862
955
|
local last="" chk dft
|
|
863
|
-
# Rows parsed, for the completeness test below. Counted here because this is
|
|
864
|
-
# the one place every row passes through.
|
|
865
|
-
_pr_rows=0
|
|
866
956
|
while IFS=" " read -r st chk dft br; do
|
|
867
957
|
[ -n "$br" ] && [ -n "$st" ] || continue
|
|
868
|
-
_pr_rows=$((_pr_rows + 1))
|
|
869
958
|
[ "$br" = "$last" ] && continue
|
|
870
959
|
last="$br"
|
|
871
960
|
# A PLAIN row (no `--rich` fields) carries `-` in the checks/draft slots so
|
|
@@ -930,10 +1019,12 @@ EOF
|
|
|
930
1019
|
# records on Bitbucket, where `bb pr list` is silently partial past 50 PRs per
|
|
931
1020
|
# state.
|
|
932
1021
|
#
|
|
933
|
-
# Counted from the rows
|
|
934
|
-
# malformed line that the
|
|
1022
|
+
# Counted from the rows carrying the fields the `sed` anchors on, so a
|
|
1023
|
+
# malformed line that the parse skips cannot inflate the count into a false
|
|
935
1024
|
# claim of completeness. Fewer rows than the limit means the host had no more
|
|
936
|
-
# to give; equal to it means it may have.
|
|
1025
|
+
# to give; equal to it means it may have. BOTH PAGES must be short: the
|
|
1026
|
+
# `open` page is a listing of its own, and a truncated one drops open PRs
|
|
1027
|
+
# the filtered `all` page no longer carries.
|
|
937
1028
|
#
|
|
938
1029
|
# AN EMPTY LIST IS NOT A COMPLETE ONE, and the test that caught this is the
|
|
939
1030
|
# reason it is written down. A host that exits 0 while printing nothing —
|
|
@@ -971,11 +1062,21 @@ EOF
|
|
|
971
1062
|
# unjoined branch and still answers correctly — the per-branch N+1 that #216
|
|
972
1063
|
# removed. That is why withholding the marker is always the safe direction and
|
|
973
1064
|
# is what every failure path here does.
|
|
1065
|
+
#
|
|
1066
|
+
# THE `open` CALL MUST BE WHOLE FOR EITHER CLAIM. The `all` call's OPEN rows
|
|
1067
|
+
# were dropped above, so an `open` answer that is short — exit 7, or a sweep
|
|
1068
|
+
# that did not state its completeness — leaves open PRs with no row at all,
|
|
1069
|
+
# and completeness would turn those misses into `NONE`.
|
|
1070
|
+
[ "$_v_open" = ok ] || return 0
|
|
974
1071
|
case "$host_err" in
|
|
975
1072
|
*"pr-list sweep complete"*)
|
|
976
|
-
|
|
1073
|
+
case "$open_err" in
|
|
1074
|
+
*"pr-list sweep complete"*)
|
|
1075
|
+
printf '1' > "$HOST_STATE_CACHE/.list-complete" 2>/dev/null || true ;;
|
|
1076
|
+
esac ;;
|
|
977
1077
|
*)
|
|
978
|
-
if [ "$_pr_rows" -gt 0 ] && [ "$_pr_rows" -lt "$PR_LIST_LIMIT" ]
|
|
1078
|
+
if [ "$_pr_rows" -gt 0 ] && [ "$_pr_rows" -lt "$PR_LIST_LIMIT" ] \
|
|
1079
|
+
&& [ "$_pr_open_rows" -lt "$PR_LIST_LIMIT" ] 2>/dev/null; then
|
|
979
1080
|
printf '1' > "$HOST_STATE_CACHE/.list-complete" 2>/dev/null || true
|
|
980
1081
|
fi ;;
|
|
981
1082
|
esac
|
|
@@ -2625,6 +2726,68 @@ ref_plan_file() { # $1=path in ref → temp file path, or "" when unreadable
|
|
|
2625
2726
|
printf '%s' "$out"
|
|
2626
2727
|
}
|
|
2627
2728
|
|
|
2729
|
+
# ---------------------------------------------------------------------------
|
|
2730
|
+
# A branch's own plans
|
|
2731
|
+
# ---------------------------------------------------------------------------
|
|
2732
|
+
#
|
|
2733
|
+
# A plan created with `Impl: same branch` lives ONLY on its work branch until
|
|
2734
|
+
# that branch merges. The enumeration above reads `origin/$MAIN` and finds
|
|
2735
|
+
# nothing, so the branch carrying the plan reached the pulse as an anonymous row
|
|
2736
|
+
# with `plan: ""` while `/api/board` showed the same plan as a Draft card.
|
|
2737
|
+
# Measured on Plot 2.20.0 (#972): two tabs of one board disagreeing about
|
|
2738
|
+
# whether a plan exists, which is worse than either answer alone.
|
|
2739
|
+
#
|
|
2740
|
+
# `board.ts:802-831` already solved this and the RULE is what transfers, not the
|
|
2741
|
+
# call: the scan keeps its own reading (Manifesto Principle 3) and the two stay
|
|
2742
|
+
# separate implementations. `readBranchPlans` is the reference.
|
|
2743
|
+
#
|
|
2744
|
+
# A NARROW READER RATHER THAN A REF ARGUMENT ON THE FOUR HELPERS ABOVE, and the
|
|
2745
|
+
# reason is `$REF_TMP`'s namespace. `ref_plan_file` caches by `basename "$p"` in
|
|
2746
|
+
# one flat directory, deliberately, so the filename a report prints is the
|
|
2747
|
+
# plan's own. Widening that across refs would let `origin/A:docs/plans/x.md` and
|
|
2748
|
+
# `origin/B:docs/plans/x.md` overwrite each other SILENTLY — the dedup's concern
|
|
2749
|
+
# ("one plan reported as several") inverted into two plans reported as one,
|
|
2750
|
+
# carrying whichever content was written last. A per-branch subdirectory makes
|
|
2751
|
+
# the collision unrepresentable instead of unlikely. The batch materialiser at
|
|
2752
|
+
# `:2543` is also built around one ref by construction: it keys a single
|
|
2753
|
+
# `cat-file --batch` stream by basename off one `PLAN_MODES`.
|
|
2754
|
+
#
|
|
2755
|
+
# REGULAR BLOBS ONLY, never mode 120000. A symlink blob holds its TARGET PATH as
|
|
2756
|
+
# content, so parsing one hands plot-plan-meta.sh a line of text where a plan
|
|
2757
|
+
# should be, and `$ACTIVE_DIR`'s links would double-count every indexed plan.
|
|
2758
|
+
# `$PLAN_DIR` alone is listed, with modes — which is also why the ref-space
|
|
2759
|
+
# symlink walk in `ref_plan_file` is deliberately NOT ported here: filtering the
|
|
2760
|
+
# links out first means there is no link left to follow.
|
|
2761
|
+
branch_plan_paths() { # $1=branch → regular .md blob paths under $PLAN_DIR
|
|
2762
|
+
# An UNREADABLE REF CONTRIBUTES NOTHING, silently — an empty set, exactly as
|
|
2763
|
+
# `planPathsInTree` returns. It never produces a blank or guessed plan, and
|
|
2764
|
+
# this is asked once per branch where a fetch may legitimately have missed one.
|
|
2765
|
+
git ls-tree -z "origin/$1" -- "$PLAN_DIR" </dev/null 2>/dev/null \
|
|
2766
|
+
| tr '\0' '\n' \
|
|
2767
|
+
| awk -F'\t' '$1 ~ /^100644 |^100755 / && $2 ~ /\.md$/ { print $2 }' || true
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
# One branch plan blob, materialized where plot-plan-meta.sh can parse it.
|
|
2771
|
+
#
|
|
2772
|
+
# NAMED UNDER A PER-BRANCH DIRECTORY for the collision reason above. The branch
|
|
2773
|
+
# name is sanitized because it carries `/` by convention (`bug/…`), which would
|
|
2774
|
+
# otherwise name a directory that does not exist.
|
|
2775
|
+
branch_plan_file() { # $1=branch, $2=path in that branch → temp file, or ""
|
|
2776
|
+
local branch="$1" p="$2" content dir out
|
|
2777
|
+
[ -n "$REF_TMP" ] || return 1
|
|
2778
|
+
content=$(git show "origin/$branch:$p" </dev/null 2>/dev/null) || return 1
|
|
2779
|
+
# AN EMPTY BLOB IS SKIPPED RATHER THAN PARSED (`board.ts:817-822`): a plan
|
|
2780
|
+
# file with no bytes parses to no phase, and a row with no phase belongs in no
|
|
2781
|
+
# column. The emptiness test sits beside the failure test rather than
|
|
2782
|
+
# replacing it, because the two are different faults.
|
|
2783
|
+
[ -n "$content" ] || return 1
|
|
2784
|
+
dir="$REF_TMP/branch/$(printf '%s' "$branch" | tr '/' '_')"
|
|
2785
|
+
mkdir -p "$dir" 2>/dev/null || return 1
|
|
2786
|
+
out="$dir/$(basename "$p")"
|
|
2787
|
+
printf '%s\n' "$content" > "$out" 2>/dev/null || return 1
|
|
2788
|
+
printf '%s' "$out"
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2628
2791
|
# Resolve which plans to report on.
|
|
2629
2792
|
#
|
|
2630
2793
|
# TWO PARALLEL ARRAYS, because a plan now has two paths that must not be
|
|
@@ -3013,6 +3176,56 @@ else
|
|
|
3013
3176
|
cand_ids+=("$plan_path")
|
|
3014
3177
|
cand_reads+=("$plan_blob")
|
|
3015
3178
|
done <<< "$(ref_ls "$PLAN_DIR")"
|
|
3179
|
+
|
|
3180
|
+
# THEN EACH PREFIXED BRANCH'S OWN TREE, for the plans `origin/$MAIN` does
|
|
3181
|
+
# not carry. Appended to the SAME candidate arrays, before the one
|
|
3182
|
+
# `parse_plan_estate` call below: a second call would build a second
|
|
3183
|
+
# `plan_meta_files` index and the lookup at the row loop keys on the file
|
|
3184
|
+
# path, so the branch plans would parse and then be unfindable.
|
|
3185
|
+
#
|
|
3186
|
+
# From here the existing pipeline carries the plan unchanged — it names its
|
|
3187
|
+
# branch in `## Slices`, the wave walk finds it, and the branch stops
|
|
3188
|
+
# reaching the report through the plan-less loop in `fleet.ts`.
|
|
3189
|
+
#
|
|
3190
|
+
# THE DEDUP IS THE BOARD'S, COPIED RATHER THAN RE-DERIVED. `on_default` is
|
|
3191
|
+
# every plan path the default branch carries; `seen_branch_plans` is every
|
|
3192
|
+
# path already taken from an earlier branch. Two branches cut from one point
|
|
3193
|
+
# carry the SAME plan file, and without the second test one plan reports as
|
|
3194
|
+
# several — a regression the board measured and fixed, and the reason its
|
|
3195
|
+
# comment exists.
|
|
3196
|
+
#
|
|
3197
|
+
# WHICH BRANCHES: `REMOTE_REFS`, already read once above, filtered by
|
|
3198
|
+
# `PREFIX_RE` — the same population the board calls a prefixed branch. No
|
|
3199
|
+
# second `for-each-ref`. The narrowing to PR-less branches the slice line
|
|
3200
|
+
# offered was WITHDRAWN by the plan's Design section: it was a fallback
|
|
3201
|
+
# against a cost that does not exist. Measured 2026-09-24, one `ls-tree`
|
|
3202
|
+
# over the plan directory is ~0.00 s and the whole addition 0.24 s, 0.4% of
|
|
3203
|
+
# a scan whose wall time is 95% waiting.
|
|
3204
|
+
on_default=$'\n'"$(ref_ls "$PLAN_DIR")"$'\n'
|
|
3205
|
+
seen_branch_plans=$'\n'
|
|
3206
|
+
while IFS=$'\t' read -r branch_name _branch_sha; do
|
|
3207
|
+
[ -n "$branch_name" ] || continue
|
|
3208
|
+
[ "$branch_name" = "HEAD" ] && continue
|
|
3209
|
+
[ "$branch_name" = "$MAIN" ] && continue
|
|
3210
|
+
printf '%s' "$branch_name" | grep -Eq "^($PREFIX_RE)/" || continue
|
|
3211
|
+
while IFS= read -r bp; do
|
|
3212
|
+
[ -n "$bp" ] || continue
|
|
3213
|
+
case "$on_default" in *$'\n'"$bp"$'\n'*) continue ;; esac
|
|
3214
|
+
case "$seen_branch_plans" in *$'\n'"$bp"$'\n'*) continue ;; esac
|
|
3215
|
+
plan_blob=$(branch_plan_file "$branch_name" "$bp") || continue
|
|
3216
|
+
[ -n "$plan_blob" ] || continue
|
|
3217
|
+
# MARKED SEEN ONLY ONCE IT IS TAKEN, matching `board.ts:823`: a blob
|
|
3218
|
+
# that could not be read has not been reported, so a later branch
|
|
3219
|
+
# carrying a readable copy of the same path must still get its turn.
|
|
3220
|
+
seen_branch_plans="${seen_branch_plans}${bp}"$'\n'
|
|
3221
|
+
# THE IDENTITY STAYS THE RELATIVE PATH. The row loop parses
|
|
3222
|
+
# `plan_reads[i]` and never re-reads by the identity in `plans[i]`, so
|
|
3223
|
+
# the `docs/plans/…md` path is a usable id — and the dedup above is what
|
|
3224
|
+
# guarantees it cannot collide with a default-branch plan's.
|
|
3225
|
+
cand_ids+=("$bp")
|
|
3226
|
+
cand_reads+=("$plan_blob")
|
|
3227
|
+
done <<< "$(branch_plan_paths "$branch_name")"
|
|
3228
|
+
done <<< "$REMOTE_REFS"
|
|
3016
3229
|
else
|
|
3017
3230
|
for plan_path in "$PLAN_DIR"*.md; do
|
|
3018
3231
|
[ -e "$plan_path" ] || continue
|
|
@@ -3066,7 +3279,7 @@ if [ ${#plans[@]} -eq 0 ]; then
|
|
|
3066
3279
|
# the scan globbed it; pointing a reader at the index would now send them to
|
|
3067
3280
|
# look for the cause of an empty list in a directory nothing consults.
|
|
3068
3281
|
echo "No plans found in ${PLAN_DIR}."
|
|
3069
|
-
echo "summary: plans=0 waves=0 branches=0 claimed=0 eligible=0 blocked=0 deferred=0 waiting=0 prereq_missing=0 main=$MAIN"
|
|
3282
|
+
echo "summary: plans=0 waves=0 branches=0 claimed=0 claimable=0 eligible=0 blocked=0 deferred=0 waiting=0 prereq_missing=0 main=$MAIN"
|
|
3070
3283
|
exit 0
|
|
3071
3284
|
fi
|
|
3072
3285
|
fi
|
|
@@ -3494,6 +3707,12 @@ n_plans=0 n_waves=0 n_branches=0 n_claimed=0 n_eligible=0 n_blocked=0 n_deferred
|
|
|
3494
3707
|
# the footer keeps the two words apart for that reason.
|
|
3495
3708
|
n_waiting=0 n_prereq_missing=0
|
|
3496
3709
|
claimable=()
|
|
3710
|
+
# HOW MANY BRANCHES AN ELIGIBLE WAVE HOLDS THAT SOMEBODY IS ON — the fact that
|
|
3711
|
+
# separates "this estate has no work" from "every candidate here is taken", and
|
|
3712
|
+
# the only reason `--list-eligible` can say which of the two its silence means.
|
|
3713
|
+
# Counted across eligible waves only: a taken branch in a blocked wave was never
|
|
3714
|
+
# a candidate, so reporting it would name work the offer path never considered.
|
|
3715
|
+
n_taken_in_eligible=0
|
|
3497
3716
|
plan_files=()
|
|
3498
3717
|
# `--why-nothing`'s input: one `verdict<TAB>name:state|...` line per slice, in
|
|
3499
3718
|
# plan order. Accumulated in the SAME loop that renders the branches, so the
|
|
@@ -3807,16 +4026,36 @@ for plan in "${plans[@]}"; do
|
|
|
3807
4026
|
wname=$(printf '%s' "$states" | awk -F'\t' -v w="$wid" '$1==w {print $7; exit}')
|
|
3808
4027
|
[ "$wname" = "-" ] && wname=""
|
|
3809
4028
|
|
|
3810
|
-
|
|
4029
|
+
# THE HEADER IS HELD, NOT PRINTED — it carries a suffix that only the
|
|
4030
|
+
# branch loop below can decide. `eligible` answers *are this wave's
|
|
4031
|
+
# prerequisites met?* while every offer path answers *can a branch here be
|
|
4032
|
+
# claimed now?*, and a wave whose branches are all taken satisfies the first
|
|
4033
|
+
# and not the second. Reported 2026-09-25 (#994) from a Bitbucket estate:
|
|
4034
|
+
# one wave `eligible`, eleven branches, both offer paths silent. The two
|
|
4035
|
+
# computations are correct and the word was carrying both answers.
|
|
4036
|
+
#
|
|
4037
|
+
# The line still prints BEFORE its branches — only the decision is delayed,
|
|
4038
|
+
# not the reading order — so the branch lines are buffered and flushed with
|
|
4039
|
+
# it. There is exactly one `echo` in that loop, which is why buffering it
|
|
4040
|
+
# costs less than threading the wave header past the six counters,
|
|
4041
|
+
# `json_branches` and `outlook_branches` the loop also builds.
|
|
4042
|
+
wave_header=" ${wname:-(unnamed)} — $verdict"
|
|
3811
4043
|
# A degradation that says nothing is indistinguishable from a bug.
|
|
3812
4044
|
# When --loose falls back to strict because the rollup cannot be had,
|
|
3813
4045
|
# say so — an operator who passed the flag and sees strict behaviour
|
|
3814
4046
|
# has no way to tell "the rollup said not-green" from "the rollup
|
|
3815
4047
|
# could not be had". Both are correct refusals; only one is about
|
|
3816
4048
|
# their PR.
|
|
4049
|
+
wave_body=""
|
|
3817
4050
|
if [ "$quiet" != 1 ] && [ "$loose" = 1 ] && [ -n "$_loose_degraded_branches" ]; then
|
|
3818
|
-
|
|
4051
|
+
wave_body+=" (--loose degraded to strict: checks unavailable for ${_loose_degraded_branches})"$'\n'
|
|
3819
4052
|
fi
|
|
4053
|
+
# HOW MANY BRANCHES OF THIS WAVE ARE TAKEN, and how many claimable. Counted
|
|
4054
|
+
# here rather than derived from the estate-wide `n_eligible`, which spans
|
|
4055
|
+
# plans. `unknown` is in neither: the host could not be asked, so the branch
|
|
4056
|
+
# is not taken and not free, and a wave holding one must never be reported
|
|
4057
|
+
# as somebody else's work.
|
|
4058
|
+
wave_taken=0 wave_free=0
|
|
3820
4059
|
json_branches=""
|
|
3821
4060
|
# The outlook's reading of this slice, built alongside the render. EVERY
|
|
3822
4061
|
# branch including the deferred ones, in the plan's order — the rule needs
|
|
@@ -3867,8 +4106,17 @@ for plan in "${plans[@]}"; do
|
|
|
3867
4106
|
if [ "${wave_claimable:$((branch_i - 1)):1}" = "1" ]; then
|
|
3868
4107
|
n_eligible=$((n_eligible + 1))
|
|
3869
4108
|
claimable+=("$br")
|
|
4109
|
+
wave_free=$((wave_free + 1))
|
|
3870
4110
|
fi
|
|
3871
|
-
|
|
4111
|
+
# TAKEN MEANS SOMEBODY HAS IT — claimed, or carrying pushed work. Read
|
|
4112
|
+
# from the branch's own state rather than from the claimable flag above,
|
|
4113
|
+
# because that flag is 0 for six different reasons and only these two mean
|
|
4114
|
+
# a person is on it. `unknown` is excluded by naming the two states rather
|
|
4115
|
+
# than by negating the flag.
|
|
4116
|
+
case "$st" in
|
|
4117
|
+
claimed|wip) wave_taken=$((wave_taken + 1)) ;;
|
|
4118
|
+
esac
|
|
4119
|
+
wave_body+=" $br — $note"$'\n'
|
|
3872
4120
|
if [ "$build_doc" = 1 ]; then
|
|
3873
4121
|
# The INTERNAL state ($st), never the prose label ($note): the board
|
|
3874
4122
|
# must not parse a string that exists for humans to read.
|
|
@@ -4067,6 +4315,45 @@ for plan in "${plans[@]}"; do
|
|
|
4067
4315
|
fi
|
|
4068
4316
|
done <<< "$states"
|
|
4069
4317
|
|
|
4318
|
+
# WHICH QUESTION THE WORD ANSWERED, said in the estate's existing
|
|
4319
|
+
# vocabulary. `someone-is-on-it` is `StartabilityVerdictSchema`'s word
|
|
4320
|
+
# (`packages/board/src/contract/schema.ts`) for exactly these two branch
|
|
4321
|
+
# states, so the wave line and its branch lines now agree rather than
|
|
4322
|
+
# offering a reader two answers to compare.
|
|
4323
|
+
#
|
|
4324
|
+
# A PROSE SUFFIX, NOT A SIXTH VERDICT. `$verdict` is untouched and reaches
|
|
4325
|
+
# `--json`, `--stream` and the outlook byte-identical: `FleetWaveSchema`'s
|
|
4326
|
+
# verdict is a strict enum, so a parsed pulse cannot carry a new word and
|
|
4327
|
+
# widening it is a separate change with its own consumers.
|
|
4328
|
+
#
|
|
4329
|
+
# THE RULE, STATED: a wave line carries BOTH answers — the verdict, then who
|
|
4330
|
+
# has it — and the suffix appears exactly when the wave is `eligible`, holds
|
|
4331
|
+
# no free branch, and holds at least one branch that is `claimed` or `wip`.
|
|
4332
|
+
# A wave with a free branch keeps bare `eligible`, which is what the offer
|
|
4333
|
+
# paths will confirm; a wave whose only non-free branches are `unknown`
|
|
4334
|
+
# keeps it too, because the host could not be asked and nobody has claimed
|
|
4335
|
+
# anything.
|
|
4336
|
+
#
|
|
4337
|
+
# `wip` COUNTS AS TAKEN, which the plan settles three times: *"a branch
|
|
4338
|
+
# counts as taken when it is claimed or in progress"*. A one-branch wave
|
|
4339
|
+
# whose branch is `wip` therefore reads `eligible — someone-is-on-it`, and
|
|
4340
|
+
# two tests in `fleet.test.mjs` asserted `/ — eligible$/` on exactly that
|
|
4341
|
+
# shape. Their subject is the VERDICT — *"has not settled its wave"*,
|
|
4342
|
+
# *"the wave must stay open"* — so the anchor moved to the verdict rather
|
|
4343
|
+
# than the suffix being suppressed; the `$` was free before a suffix existed
|
|
4344
|
+
# and asserted a second thing neither test meant. The other five sites
|
|
4345
|
+
# carrying that anchor hold a free branch and are untouched.
|
|
4346
|
+
if [ "$verdict" = "eligible" ] && [ "$wave_free" -eq 0 ] && [ "$wave_taken" -gt 0 ]; then
|
|
4347
|
+
wave_header+=" — someone-is-on-it"
|
|
4348
|
+
fi
|
|
4349
|
+
if [ "$verdict" = "eligible" ]; then
|
|
4350
|
+
n_taken_in_eligible=$((n_taken_in_eligible + wave_taken))
|
|
4351
|
+
fi
|
|
4352
|
+
if [ "$quiet" != 1 ]; then
|
|
4353
|
+
echo "$wave_header"
|
|
4354
|
+
[ -n "$wave_body" ] && printf '%s' "$wave_body"
|
|
4355
|
+
fi
|
|
4356
|
+
|
|
4070
4357
|
if [ "$build_doc" = 1 ]; then
|
|
4071
4358
|
json_waves+="${json_waves:+,}{\"name\":\"$(json_str "$wname")\""
|
|
4072
4359
|
json_waves+=",\"verdict\":\"$verdict\",\"branches\":[$json_branches]}"
|
|
@@ -4148,6 +4435,27 @@ fi
|
|
|
4148
4435
|
# "Nothing to start" is a normal state, not a failure — the exit code is what
|
|
4149
4436
|
# distinguishes it from a name, so callers can branch on it without parsing.
|
|
4150
4437
|
if [ "$next_only" = 1 ]; then
|
|
4438
|
+
# WHY THE LIST IS EMPTY, for `--list-eligible` only, and ON STDERR. An
|
|
4439
|
+
# operator reading the body's `eligible` and then getting silence here cannot
|
|
4440
|
+
# tell a claimed-out estate from an empty one, and #994 is that reading: one
|
|
4441
|
+
# wave eligible, eleven branches, nothing offered. The exit code still carries
|
|
4442
|
+
# the answer — 1 either way — so no caller's gate moves.
|
|
4443
|
+
#
|
|
4444
|
+
# STDERR BECAUSE STDOUT IS A TARGET LIST. `plot-dispatch.sh:3475` pipes this
|
|
4445
|
+
# stdout through `sort -u` and dispatches every line, so a sentence there
|
|
4446
|
+
# becomes a branch name it tries to claim. That caller already discards
|
|
4447
|
+
# stderr (`2>/dev/null`), so the sentence reaches a person and no machine.
|
|
4448
|
+
#
|
|
4449
|
+
# `--next` IS UNTOUCHED: its exit-1 contract is shipped, documented and
|
|
4450
|
+
# tested twice, and it names ONE branch for a worker that has nothing to read
|
|
4451
|
+
# a sentence with.
|
|
4452
|
+
if [ ${#claimable[@]} -eq 0 ] && [ "$list_all" = 1 ]; then
|
|
4453
|
+
if [ "$n_taken_in_eligible" -gt 0 ]; then
|
|
4454
|
+
echo "nothing claimable: $n_taken_in_eligible branch(es) in eligible waves are taken." >&2
|
|
4455
|
+
else
|
|
4456
|
+
echo "nothing claimable: no eligible wave holds a startable branch." >&2
|
|
4457
|
+
fi
|
|
4458
|
+
fi
|
|
4151
4459
|
[ ${#claimable[@]} -gt 0 ] || exit 1
|
|
4152
4460
|
if [ "$list_all" = 1 ]; then
|
|
4153
4461
|
printf '%s\n' "${claimable[@]}"
|
|
@@ -4455,4 +4763,4 @@ if [ "$build_doc" = 1 ] && [ "$record" = 1 ] && [ -n "$reading_doc" ]; then
|
|
|
4455
4763
|
fi
|
|
4456
4764
|
write_bridge
|
|
4457
4765
|
echo "Pulse complete. This report is derived — nothing was changed."
|
|
4458
|
-
echo "summary: plans=$n_plans waves=$n_waves branches=$n_branches claimed=$n_claimed eligible=$n_eligible blocked=$n_blocked deferred=$n_deferred waiting=$n_waiting prereq_missing=$n_prereq_missing merge_detect=$MERGE_DETECT host=$HOST_VERDICT main=$MAIN"
|
|
4766
|
+
echo "summary: plans=$n_plans waves=$n_waves branches=$n_branches claimed=$n_claimed claimable=$n_eligible eligible=$n_eligible blocked=$n_blocked deferred=$n_deferred waiting=$n_waiting prereq_missing=$n_prereq_missing merge_detect=$MERGE_DETECT host=$HOST_VERDICT main=$MAIN"
|