@plot-pm/board 0.16.3 → 0.17.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 +242 -153
- package/package.json +3 -4
- package/plot-agent-manifest.sh +0 -35
- package/plot-config.sh +14 -14
- package/plot-dispatch.sh +29 -26
- package/plot-fleet-scan.sh +154 -141
- package/plot-host.sh +37 -58
- package/plot-monitor-subject.sh +7 -8
- package/plot-worker-state.sh +10 -163
- package/plot-build-monitor.sh +0 -480
- package/plot-transcript-quiet.sh +0 -172
package/plot-fleet-scan.sh
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Plot helper: fleet pulse — deterministic extractor for wave/claim state.
|
|
3
|
-
# Usage: plot-fleet-scan.sh [--no-fetch] [--offline] [--next] [<slug>]
|
|
3
|
+
# Usage: plot-fleet-scan.sh [--no-fetch] [--offline] [--next] [--slice-names] [<slug>]
|
|
4
|
+
# --slice-names print every slice a `waits:` name may wait on, one per line,
|
|
5
|
+
# and exit: the estate's non-deferred slices of non-terminal plans,
|
|
6
|
+
# whatever slug is given. No fetch, no host call.
|
|
4
7
|
# --no-fetch skip `git fetch`
|
|
5
8
|
# --offline same (no fetch) — used for cheap, ambient pulses.
|
|
6
9
|
# The fetch also PRUNES remote-tracking refs, so skipping it
|
|
@@ -76,11 +79,12 @@
|
|
|
76
79
|
# Branch states — the word each BRANCH carries, distinct from the wave verdicts
|
|
77
80
|
# above. `open`, `wip`, `merged`, `claimed`, `deferred` and `unknown` are read
|
|
78
81
|
# from git and the host. Two more are read from the plan's `waits:` annotation:
|
|
79
|
-
# waiting the branch names a prerequisite branch that has not merged
|
|
82
|
+
# waiting the branch names a prerequisite branch that has not merged —
|
|
83
|
+
# including a slice some plan names that nobody has started. A
|
|
80
84
|
# wait with an end: it clears when that branch lands, and the
|
|
81
85
|
# fleet payload carries `waits_on` so a reader sees on WHAT.
|
|
82
86
|
# blocked the branch names a prerequisite the host has never seen a PR
|
|
83
|
-
# for
|
|
87
|
+
# for and no plan names as a slice — a typo. A defect in the plan
|
|
84
88
|
# estate, not progress, which is why it is a separate word: the
|
|
85
89
|
# first resolves by waiting, the second by editing the plan.
|
|
86
90
|
# THE SAME WORD AS THE WAVE VERDICT, IN A DIFFERENT VOCABULARY.
|
|
@@ -261,6 +265,7 @@ do_fetch=1
|
|
|
261
265
|
next_only=0
|
|
262
266
|
list_all=0
|
|
263
267
|
why_nothing=0
|
|
268
|
+
slice_names=0
|
|
264
269
|
loose=0
|
|
265
270
|
log_pulse=0
|
|
266
271
|
as_json=0
|
|
@@ -295,6 +300,7 @@ while [ $# -gt 0 ]; do
|
|
|
295
300
|
# about the SAME plans `--next` was silent over — a terminal plan admitted
|
|
296
301
|
# here would answer `not-yet` about work somebody decided was not needed.
|
|
297
302
|
--why-nothing) next_only=1; why_nothing=1 ;;
|
|
303
|
+
--slice-names) slice_names=1; do_fetch=0 ;;
|
|
298
304
|
# `--json` ASSEMBLES BUT DOES NOT RECORD, and the two flags differ here for
|
|
299
305
|
# a reason. `--stream` is what the BOARD spawns (`fleet.ts:2694`) and
|
|
300
306
|
# `--log-pulse` is what `/plot-pulse` passes: both produce a pulse somebody
|
|
@@ -1535,10 +1541,7 @@ merged_by_host() { # $1=branch → 0 when the host reports its PR MERGED
|
|
|
1535
1541
|
# backend, a host returning 503 all afternoon — must not manufacture the state
|
|
1536
1542
|
# that tells a reader to stop looking. It falls through to the local signals,
|
|
1537
1543
|
# so a branch with work on the floor reads `stalled`: go and look. That is the
|
|
1538
|
-
# safe direction for an answer nobody could verify.
|
|
1539
|
-
reached_review() { # $1=branch → 0 when an open or merged PR exists
|
|
1540
|
-
case "$(host_pr_state "$1")" in OPEN|MERGED) return 0 ;; *) return 1 ;; esac
|
|
1541
|
-
}
|
|
1544
|
+
# safe direction for an answer nobody could verify. `worker_of` asks it.
|
|
1542
1545
|
|
|
1543
1546
|
# ---------------------------------------------------------------------------
|
|
1544
1547
|
# A SLICE THAT WAITS ON ANOTHER PLAN'S BRANCH
|
|
@@ -1571,9 +1574,9 @@ reached_review() { # $1=branch → 0 when an open or merged PR exists
|
|
|
1571
1574
|
# the branch it was cut from, which is why `plot-pr-merged.sh` reads PRs and not
|
|
1572
1575
|
# refs, and why this reads the same source.
|
|
1573
1576
|
#
|
|
1574
|
-
# WHAT THE HOST SAID, AND NOT WHAT IT MEANS.
|
|
1577
|
+
# WHAT THE HOST SAID, AND NOT WHAT IT MEANS. The prerequisite reading answered
|
|
1575
1578
|
# `waiting` / `blocked` / `""` until the derivation moved: the three answers and
|
|
1576
|
-
#
|
|
1579
|
+
# why only `NONE` on a name outside `SLICE_NAMES` means `blocked` are `waitVerdict` in
|
|
1577
1580
|
# `packages/domain/src/rules/branch-state.ts`, with a test per case. What stays
|
|
1578
1581
|
# here is the READING and the cost argument above it, which is a fact about
|
|
1579
1582
|
# this script's host budget rather than about what a wait means.
|
|
@@ -1582,9 +1585,8 @@ reached_review() { # $1=branch → 0 when an open or merged PR exists
|
|
|
1582
1585
|
# may legitimately omit: its plan may be delivered and its ref gone. The bound
|
|
1583
1586
|
# is the same one PR #216 set — ABSENT branches, not all branches — and the
|
|
1584
1587
|
# cache above keeps it at one call per run.
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
}
|
|
1588
|
+
# `host_pr_state "$name" --ask` → OPEN|MERGED|CLOSED|NONE|-, called once per
|
|
1589
|
+
# prerequisite name in the refill below.
|
|
1588
1590
|
|
|
1589
1591
|
# Modification time of a path, in epoch seconds, following symlinks — or "" when
|
|
1590
1592
|
# it cannot be read.
|
|
@@ -2140,10 +2142,10 @@ worker_of() { # $1=branch → "state\tpid\texit"
|
|
|
2140
2142
|
#
|
|
2141
2143
|
# `$st` IS NOT THIS FACT. It answers a ref/ancestry question — a branch under
|
|
2142
2144
|
# review reads `wip` — and `merged` there can come from a merge subject with
|
|
2143
|
-
# no PR behind it at all.
|
|
2145
|
+
# no PR behind it at all. The PR state below is the one question that
|
|
2144
2146
|
# outranks the local signals: has this work left the worker's hands?
|
|
2145
2147
|
local pr_fact=""
|
|
2146
|
-
|
|
2148
|
+
case "$(host_pr_state "$br")" in OPEN|MERGED) pr_fact="pr" ;; esac
|
|
2147
2149
|
plot_worker_state "$wt" "$pr_fact"
|
|
2148
2150
|
}
|
|
2149
2151
|
|
|
@@ -2993,13 +2995,7 @@ add_ref_plan() { # $1=path in ref
|
|
|
2993
2995
|
# `$PLAN_DIR` by default, and their symlinks resolve to files already
|
|
2994
2996
|
# enumerated — counting both would double every plan. `git ls-tree` without
|
|
2995
2997
|
# `-r` lists one level, and the worktree glob `"$PLAN_DIR"*.md` does not
|
|
2996
|
-
# descend either.
|
|
2997
|
-
is_plan_phase() { # $1=normalized phase → 0 when this file is a plan
|
|
2998
|
-
case "$1" in
|
|
2999
|
-
""|NONE) return 1 ;;
|
|
3000
|
-
*) return 0 ;;
|
|
3001
|
-
esac
|
|
3002
|
-
}
|
|
2998
|
+
# descend either. `add_plan_by_phase` below applies the rule.
|
|
3003
2999
|
|
|
3004
3000
|
# ---------------------------------------------------------------------------
|
|
3005
3001
|
# ONE PARSE FOR THE WHOLE ESTATE
|
|
@@ -3229,8 +3225,7 @@ for line in sys.stdin:
|
|
|
3229
3225
|
# would have changed was never findable and stays unfindable. The compare is
|
|
3230
3226
|
# literal, so a path holding `[`, `*` or `?` matches itself and never a glob.
|
|
3231
3227
|
# THE SEARCH ITSELF, assigning to `plan_meta_index_reply`. Run it in the shell
|
|
3232
|
-
# whose cursor should advance
|
|
3233
|
-
# for callers that are already inside a `$(…)`.
|
|
3228
|
+
# whose cursor should advance.
|
|
3234
3229
|
plan_meta_index_reply=""
|
|
3235
3230
|
plan_meta_index_into() { # $1=file → sets plan_meta_index_reply
|
|
3236
3231
|
plan_meta_index_reply=""
|
|
@@ -3259,37 +3254,13 @@ plan_meta_index_into() { # $1=file → sets plan_meta_index_reply
|
|
|
3259
3254
|
done
|
|
3260
3255
|
}
|
|
3261
3256
|
|
|
3262
|
-
# The
|
|
3263
|
-
#
|
|
3264
|
-
#
|
|
3265
|
-
# weight was never there.
|
|
3266
|
-
plan_meta_index_of() { # $1=file → index on stdout, or ""
|
|
3267
|
-
plan_meta_index_into "$1"
|
|
3268
|
-
printf '%s' "$plan_meta_index_reply"
|
|
3269
|
-
}
|
|
3270
|
-
|
|
3271
|
-
# The phase a file declares, or "" when it is not a plan. Read from the single
|
|
3272
|
-
# estate parse above rather than spawned per file.
|
|
3273
|
-
plan_phase_of() { # $1=file to parse → normalized phase on stdout
|
|
3274
|
-
local i
|
|
3275
|
-
i=$(plan_meta_index_of "$1")
|
|
3276
|
-
[ -n "$i" ] || { printf ''; return 0; }
|
|
3277
|
-
printf '%s' "${plan_meta_phases[$i]}"
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
# The same answer, ASSIGNED to `plan_phase_reply` instead of printed.
|
|
3281
|
-
#
|
|
3282
|
-
# THE CURSOR ONLY ADVANCES IN THE PARENT SHELL. `plan_phase_of` is read as
|
|
3283
|
-
# `$(plan_phase_of …)` and `plan_meta_index_of` as `$(plan_meta_index_of …)`,
|
|
3284
|
-
# and a subshell's write to `plan_meta_cursor` is discarded when it exits — so
|
|
3285
|
-
# the resumable search would restart from 0 on every call and the quadratic
|
|
3286
|
-
# shape would survive the fix. This form runs in the caller's own shell, which
|
|
3287
|
-
# is where `add_plan_by_phase` runs and where the 416 asks come from.
|
|
3257
|
+
# The phase a file declares, or "" when it is not a plan, ASSIGNED to
|
|
3258
|
+
# `plan_phase_reply`. Read from the single estate parse above rather than
|
|
3259
|
+
# spawned per file.
|
|
3288
3260
|
#
|
|
3289
|
-
#
|
|
3290
|
-
#
|
|
3291
|
-
#
|
|
3292
|
-
# would trade a one-off subshell for a less obvious assignment.
|
|
3261
|
+
# NO STDOUT FORM. A subshell's write to `plan_meta_cursor` is discarded when it
|
|
3262
|
+
# exits, so a `$(…)` reader would restart the search from 0 on every call. Every
|
|
3263
|
+
# caller of this and of `plan_meta_index_into` runs in the scan's own shell.
|
|
3293
3264
|
plan_phase_reply=""
|
|
3294
3265
|
plan_phase_into() { # $1=file to parse → sets plan_phase_reply
|
|
3295
3266
|
plan_phase_reply=""
|
|
@@ -3314,6 +3285,80 @@ is_terminal_phase() { # $1=normalized phase → 0 when finished
|
|
|
3314
3285
|
esac
|
|
3315
3286
|
}
|
|
3316
3287
|
|
|
3288
|
+
# EVERY PLAN FILE OF THE ESTATE, into `cand_ids` (identity) and `cand_reads`
|
|
3289
|
+
# (the file to parse): the plan directory of `origin/$MAIN`, then each prefixed
|
|
3290
|
+
# branch's plans the default branch does not carry. A named slug reads one plan
|
|
3291
|
+
# and still enumerates the estate, because `SLICE_NAMES` is the estate's set.
|
|
3292
|
+
enumerate_estate() {
|
|
3293
|
+
cand_ids=()
|
|
3294
|
+
cand_reads=()
|
|
3295
|
+
if [ "$PLAN_SOURCE" = "ref" ]; then
|
|
3296
|
+
while IFS= read -r plan_path; do
|
|
3297
|
+
[ -n "$plan_path" ] || continue
|
|
3298
|
+
plan_blob=$(ref_plan_file "$plan_path") || continue
|
|
3299
|
+
[ -n "$plan_blob" ] || continue
|
|
3300
|
+
cand_ids+=("$plan_path")
|
|
3301
|
+
cand_reads+=("$plan_blob")
|
|
3302
|
+
done <<< "$(ref_ls "$PLAN_DIR")"
|
|
3303
|
+
|
|
3304
|
+
# THEN EACH PREFIXED BRANCH'S OWN TREE, for the plans `origin/$MAIN` does
|
|
3305
|
+
# not carry. Appended to the SAME candidate arrays, before the one
|
|
3306
|
+
# `parse_plan_estate` call below: a second call would build a second
|
|
3307
|
+
# `plan_meta_files` index and the lookup at the row loop keys on the file
|
|
3308
|
+
# path, so the branch plans would parse and then be unfindable.
|
|
3309
|
+
#
|
|
3310
|
+
# From here the existing pipeline carries the plan unchanged — it names its
|
|
3311
|
+
# branch in `## Slices`, the wave walk finds it, and the branch stops
|
|
3312
|
+
# reaching the report through the plan-less loop in `fleet.ts`.
|
|
3313
|
+
#
|
|
3314
|
+
# THE DEDUP IS THE BOARD'S, COPIED RATHER THAN RE-DERIVED. `on_default` is
|
|
3315
|
+
# every plan path the default branch carries; `seen_branch_plans` is every
|
|
3316
|
+
# path already taken from an earlier branch. Two branches cut from one point
|
|
3317
|
+
# carry the SAME plan file, and without the second test one plan reports as
|
|
3318
|
+
# several — a regression the board measured and fixed, and the reason its
|
|
3319
|
+
# comment exists.
|
|
3320
|
+
#
|
|
3321
|
+
# WHICH BRANCHES: `REMOTE_REFS`, already read once above, filtered by
|
|
3322
|
+
# `PREFIX_RE` — the same population the board calls a prefixed branch. No
|
|
3323
|
+
# second `for-each-ref`. The narrowing to PR-less branches the slice line
|
|
3324
|
+
# offered was WITHDRAWN by the plan's Design section: it was a fallback
|
|
3325
|
+
# against a cost that does not exist. Measured 2026-09-24, one `ls-tree`
|
|
3326
|
+
# over the plan directory is ~0.00 s and the whole addition 0.24 s, 0.4% of
|
|
3327
|
+
# a scan whose wall time is 95% waiting.
|
|
3328
|
+
on_default=$'\n'"$(ref_ls "$PLAN_DIR")"$'\n'
|
|
3329
|
+
seen_branch_plans=$'\n'
|
|
3330
|
+
while IFS=$'\t' read -r branch_name _branch_sha; do
|
|
3331
|
+
[ -n "$branch_name" ] || continue
|
|
3332
|
+
[ "$branch_name" = "HEAD" ] && continue
|
|
3333
|
+
[ "$branch_name" = "$MAIN" ] && continue
|
|
3334
|
+
printf '%s' "$branch_name" | grep -Eq "^($PREFIX_RE)/" || continue
|
|
3335
|
+
while IFS= read -r bp; do
|
|
3336
|
+
[ -n "$bp" ] || continue
|
|
3337
|
+
case "$on_default" in *$'\n'"$bp"$'\n'*) continue ;; esac
|
|
3338
|
+
case "$seen_branch_plans" in *$'\n'"$bp"$'\n'*) continue ;; esac
|
|
3339
|
+
plan_blob=$(branch_plan_file "$branch_name" "$bp") || continue
|
|
3340
|
+
[ -n "$plan_blob" ] || continue
|
|
3341
|
+
# MARKED SEEN ONLY ONCE IT IS TAKEN, matching `board.ts:823`: a blob
|
|
3342
|
+
# that could not be read has not been reported, so a later branch
|
|
3343
|
+
# carrying a readable copy of the same path must still get its turn.
|
|
3344
|
+
seen_branch_plans="${seen_branch_plans}${bp}"$'\n'
|
|
3345
|
+
# THE IDENTITY STAYS THE RELATIVE PATH. The row loop parses
|
|
3346
|
+
# `plan_reads[i]` and never re-reads by the identity in `plans[i]`, so
|
|
3347
|
+
# the `docs/plans/…md` path is a usable id — and the dedup above is what
|
|
3348
|
+
# guarantees it cannot collide with a default-branch plan's.
|
|
3349
|
+
cand_ids+=("$bp")
|
|
3350
|
+
cand_reads+=("$plan_blob")
|
|
3351
|
+
done <<< "$(branch_plan_paths "$branch_name")"
|
|
3352
|
+
done <<< "$REMOTE_REFS"
|
|
3353
|
+
else
|
|
3354
|
+
for plan_path in "$PLAN_DIR"*.md; do
|
|
3355
|
+
[ -e "$plan_path" ] || continue
|
|
3356
|
+
cand_ids+=("$plan_path")
|
|
3357
|
+
cand_reads+=("$plan_path")
|
|
3358
|
+
done
|
|
3359
|
+
fi
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3317
3362
|
if [ -n "$slug" ]; then
|
|
3318
3363
|
# A NAMED SLUG IS NOT A LIST, so it keeps its own resolution: the caller
|
|
3319
3364
|
# already said which plan it means, and the phase rule would only be able to
|
|
@@ -3348,7 +3393,18 @@ if [ -n "$slug" ]; then
|
|
|
3348
3393
|
# because that is where every later question reads its answer from.
|
|
3349
3394
|
if [ ${#plans[@]} -gt 0 ]; then
|
|
3350
3395
|
parse_plan_estate "${plan_reads[0]}"
|
|
3351
|
-
|
|
3396
|
+
plan_phase_into "${plan_reads[0]}"
|
|
3397
|
+
plan_phases+=("$plan_phase_reply")
|
|
3398
|
+
fi
|
|
3399
|
+
# THE WHOLE ESTATE TOO, when a `waits:` name will be looked up in
|
|
3400
|
+
# `SLICE_NAMES` below: a prerequisite that is a slice of ANOTHER plan must
|
|
3401
|
+
# read the same here as on a full scan (#1305). The plan loop reads only
|
|
3402
|
+
# `plans`, so the extra plans change no row. Asked only when needed, because
|
|
3403
|
+
# it is the cost: measured 2026-10-06 on this estate (ref mode, `--offline`),
|
|
3404
|
+
# a slug scan took 1.6 s alone and 5.2 s with the estate enumerated.
|
|
3405
|
+
if [ "$slice_names" = 1 ] || printf '%s' ${plan_meta_waves[@]+"${plan_meta_waves[@]}"} | awk -F'\t' '$5 != "-" { w = 1 } END { exit !w }'; then
|
|
3406
|
+
enumerate_estate
|
|
3407
|
+
[ ${#cand_reads[@]} -gt 0 ] && parse_plan_estate "${cand_reads[@]}"
|
|
3352
3408
|
fi
|
|
3353
3409
|
else
|
|
3354
3410
|
# ---------------------------------------------------------------------------
|
|
@@ -3414,7 +3470,8 @@ else
|
|
|
3414
3470
|
# lookup cursor's advance, which is the whole saving.
|
|
3415
3471
|
plan_phase_into "$src"
|
|
3416
3472
|
ph="$plan_phase_reply"
|
|
3417
|
-
|
|
3473
|
+
# NOT A PLAN: no phase parsed — see "What makes a file a plan" above.
|
|
3474
|
+
case "$ph" in ""|NONE) return 0 ;; esac
|
|
3418
3475
|
if is_terminal_phase "$ph"; then
|
|
3419
3476
|
[ "$next_only" = 1 ] && return 0
|
|
3420
3477
|
terminal_plans+=("$id")
|
|
@@ -3432,73 +3489,7 @@ else
|
|
|
3432
3489
|
# ref mode that means every blob is materialized first: the phase decides the
|
|
3433
3490
|
# group, so the file must exist before it can be asked, and it must be asked
|
|
3434
3491
|
# together with all the others rather than one at a time.
|
|
3435
|
-
|
|
3436
|
-
cand_reads=()
|
|
3437
|
-
if [ "$PLAN_SOURCE" = "ref" ]; then
|
|
3438
|
-
while IFS= read -r plan_path; do
|
|
3439
|
-
[ -n "$plan_path" ] || continue
|
|
3440
|
-
plan_blob=$(ref_plan_file "$plan_path") || continue
|
|
3441
|
-
[ -n "$plan_blob" ] || continue
|
|
3442
|
-
cand_ids+=("$plan_path")
|
|
3443
|
-
cand_reads+=("$plan_blob")
|
|
3444
|
-
done <<< "$(ref_ls "$PLAN_DIR")"
|
|
3445
|
-
|
|
3446
|
-
# THEN EACH PREFIXED BRANCH'S OWN TREE, for the plans `origin/$MAIN` does
|
|
3447
|
-
# not carry. Appended to the SAME candidate arrays, before the one
|
|
3448
|
-
# `parse_plan_estate` call below: a second call would build a second
|
|
3449
|
-
# `plan_meta_files` index and the lookup at the row loop keys on the file
|
|
3450
|
-
# path, so the branch plans would parse and then be unfindable.
|
|
3451
|
-
#
|
|
3452
|
-
# From here the existing pipeline carries the plan unchanged — it names its
|
|
3453
|
-
# branch in `## Slices`, the wave walk finds it, and the branch stops
|
|
3454
|
-
# reaching the report through the plan-less loop in `fleet.ts`.
|
|
3455
|
-
#
|
|
3456
|
-
# THE DEDUP IS THE BOARD'S, COPIED RATHER THAN RE-DERIVED. `on_default` is
|
|
3457
|
-
# every plan path the default branch carries; `seen_branch_plans` is every
|
|
3458
|
-
# path already taken from an earlier branch. Two branches cut from one point
|
|
3459
|
-
# carry the SAME plan file, and without the second test one plan reports as
|
|
3460
|
-
# several — a regression the board measured and fixed, and the reason its
|
|
3461
|
-
# comment exists.
|
|
3462
|
-
#
|
|
3463
|
-
# WHICH BRANCHES: `REMOTE_REFS`, already read once above, filtered by
|
|
3464
|
-
# `PREFIX_RE` — the same population the board calls a prefixed branch. No
|
|
3465
|
-
# second `for-each-ref`. The narrowing to PR-less branches the slice line
|
|
3466
|
-
# offered was WITHDRAWN by the plan's Design section: it was a fallback
|
|
3467
|
-
# against a cost that does not exist. Measured 2026-09-24, one `ls-tree`
|
|
3468
|
-
# over the plan directory is ~0.00 s and the whole addition 0.24 s, 0.4% of
|
|
3469
|
-
# a scan whose wall time is 95% waiting.
|
|
3470
|
-
on_default=$'\n'"$(ref_ls "$PLAN_DIR")"$'\n'
|
|
3471
|
-
seen_branch_plans=$'\n'
|
|
3472
|
-
while IFS=$'\t' read -r branch_name _branch_sha; do
|
|
3473
|
-
[ -n "$branch_name" ] || continue
|
|
3474
|
-
[ "$branch_name" = "HEAD" ] && continue
|
|
3475
|
-
[ "$branch_name" = "$MAIN" ] && continue
|
|
3476
|
-
printf '%s' "$branch_name" | grep -Eq "^($PREFIX_RE)/" || continue
|
|
3477
|
-
while IFS= read -r bp; do
|
|
3478
|
-
[ -n "$bp" ] || continue
|
|
3479
|
-
case "$on_default" in *$'\n'"$bp"$'\n'*) continue ;; esac
|
|
3480
|
-
case "$seen_branch_plans" in *$'\n'"$bp"$'\n'*) continue ;; esac
|
|
3481
|
-
plan_blob=$(branch_plan_file "$branch_name" "$bp") || continue
|
|
3482
|
-
[ -n "$plan_blob" ] || continue
|
|
3483
|
-
# MARKED SEEN ONLY ONCE IT IS TAKEN, matching `board.ts:823`: a blob
|
|
3484
|
-
# that could not be read has not been reported, so a later branch
|
|
3485
|
-
# carrying a readable copy of the same path must still get its turn.
|
|
3486
|
-
seen_branch_plans="${seen_branch_plans}${bp}"$'\n'
|
|
3487
|
-
# THE IDENTITY STAYS THE RELATIVE PATH. The row loop parses
|
|
3488
|
-
# `plan_reads[i]` and never re-reads by the identity in `plans[i]`, so
|
|
3489
|
-
# the `docs/plans/…md` path is a usable id — and the dedup above is what
|
|
3490
|
-
# guarantees it cannot collide with a default-branch plan's.
|
|
3491
|
-
cand_ids+=("$bp")
|
|
3492
|
-
cand_reads+=("$plan_blob")
|
|
3493
|
-
done <<< "$(branch_plan_paths "$branch_name")"
|
|
3494
|
-
done <<< "$REMOTE_REFS"
|
|
3495
|
-
else
|
|
3496
|
-
for plan_path in "$PLAN_DIR"*.md; do
|
|
3497
|
-
[ -e "$plan_path" ] || continue
|
|
3498
|
-
cand_ids+=("$plan_path")
|
|
3499
|
-
cand_reads+=("$plan_path")
|
|
3500
|
-
done
|
|
3501
|
-
fi
|
|
3492
|
+
enumerate_estate
|
|
3502
3493
|
|
|
3503
3494
|
# ONE INVOCATION FOR THE WHOLE ESTATE. Everything below reads its result.
|
|
3504
3495
|
[ ${#cand_reads[@]} -gt 0 ] && parse_plan_estate "${cand_reads[@]}"
|
|
@@ -3514,6 +3505,19 @@ else
|
|
|
3514
3505
|
done
|
|
3515
3506
|
fi
|
|
3516
3507
|
|
|
3508
|
+
# EVERY SLICE SOMEBODY MAY STILL START, newline-framed for a `case` lookup: the
|
|
3509
|
+
# non-deferred slices (wave field 3) of every parsed plan whose phase is not
|
|
3510
|
+
# terminal. Every mode parses the whole estate, so a slug run, `--next` and the
|
|
3511
|
+
# full scan hold one set, and `plot-dispatch.sh` asks `--slice-names` for it
|
|
3512
|
+
# rather than reading its own plan. A `waits:` name in the set with no pull
|
|
3513
|
+
# request reads `waiting`; any other name with none reads `blocked` (#1305).
|
|
3514
|
+
# `namedSlices` in `rules/branch-state.ts` states the same rule, and
|
|
3515
|
+
# `branch-state.corpus.test.ts` holds the two to one answer.
|
|
3516
|
+
live_waves=()
|
|
3517
|
+
for _si in "${!plan_meta_files[@]}"; do is_terminal_phase "${plan_meta_phases[$_si]}" || live_waves+=("${plan_meta_waves[$_si]}"); done
|
|
3518
|
+
SLICE_NAMES=$'\n'"$(printf '%s' ${live_waves[@]+"${live_waves[@]}"} | awk -F'\t' '$3 != "true" && !seen[$2]++ { print $2 }')"$'\n'
|
|
3519
|
+
[ "$slice_names" = 1 ] && { printf '%s' "$SLICE_NAMES" | sed '/^$/d'; exit 0; }
|
|
3520
|
+
|
|
3517
3521
|
if [ ${#plans[@]} -eq 0 ]; then
|
|
3518
3522
|
# --next/--list-eligible must stay silent and exit 1: "nothing to start" is
|
|
3519
3523
|
# the same answer whether the plans are all claimed or there are no plans at
|
|
@@ -3687,7 +3691,8 @@ if [ ${#plans[@]} -gt 0 ] && [ -n "$MERGE_SUBJECTS" ]; then
|
|
|
3687
3691
|
# — `$plan_reads[i]`, the same key the row loop uses at pass 1a. In ref mode
|
|
3688
3692
|
# that is a materialized blob under a temp path and not `$PLAN_DIR` at all,
|
|
3689
3693
|
# so a reconstructed path finds nothing.
|
|
3690
|
-
|
|
3694
|
+
plan_meta_index_into "${plan_reads[$_sp_i]}"
|
|
3695
|
+
_sp_meta_i=$plan_meta_index_reply
|
|
3691
3696
|
[ -n "$_sp_meta_i" ] || continue
|
|
3692
3697
|
_sp_branches=""
|
|
3693
3698
|
while IFS=$'\t' read -r _sp_idx _sp_br _sp_rest; do
|
|
@@ -4109,7 +4114,7 @@ branch_readings() { # $1=branch $2=deferred $3=plan-base → readings
|
|
|
4109
4114
|
# There is no shell fallback: a second implementation kept "just in case" is
|
|
4110
4115
|
# the duplication this adoption removes, and it would be the copy nobody tests.
|
|
4111
4116
|
# `plot-deliver.sh` fails the same way for the same reason.
|
|
4112
|
-
ask_branch_states() { # stdin=readings → one `state<TAB>needs` line per branch
|
|
4117
|
+
ask_branch_states() { # stdin=readings → one `state<TAB>needs<TAB>own` line per branch
|
|
4113
4118
|
node "$script_dir/board/plot-branch-state.mjs" 2>/dev/null \
|
|
4114
4119
|
|| { echo "error: cannot read branch states — run 'pnpm build:board'." >&2; exit 2; }
|
|
4115
4120
|
}
|
|
@@ -4227,7 +4232,8 @@ for plan in "${plans[@]}"; do
|
|
|
4227
4232
|
# The estate was parsed ONCE, before this loop. A plan absent from that
|
|
4228
4233
|
# result could not be read at all, which is the same answer the per-plan
|
|
4229
4234
|
# parse gave by failing — so it is skipped here exactly as it was then.
|
|
4230
|
-
|
|
4235
|
+
plan_meta_index_into "$plan_read"
|
|
4236
|
+
meta_i=$plan_meta_index_reply
|
|
4231
4237
|
[ -n "$meta_i" ] || continue
|
|
4232
4238
|
|
|
4233
4239
|
# The plan's own phase, carried onto the pulse so a consumer can derive a row
|
|
@@ -4339,7 +4345,7 @@ for plan in "${plans[@]}"; do
|
|
|
4339
4345
|
# not evidence in either direction. The two were one marker until CI ran the
|
|
4340
4346
|
# corpus with no token, where every prerequisite answers `-` and every waiting
|
|
4341
4347
|
# branch read `open`. Reading it
|
|
4342
|
-
# costs a host round trip (`
|
|
4348
|
+
# costs a host round trip (`host_pr_state … --ask`, because a delivered
|
|
4343
4349
|
# prerequisite's ref is gone and only its PR outlives it), and the scan spends
|
|
4344
4350
|
# that only where the answer could change the branch's state. Which states
|
|
4345
4351
|
# those are IS the precedence, so the rule reports it rather than this loop
|
|
@@ -4363,7 +4369,7 @@ for plan in "${plans[@]}"; do
|
|
|
4363
4369
|
# "-" is the absent marker the shim writes, for the tab-collapse reason
|
|
4364
4370
|
# above. Normalized here so everything downstream tests emptiness.
|
|
4365
4371
|
[ "$waits" = "-" ] && waits=""
|
|
4366
|
-
readings+="$(branch_readings "$br" "$deferred" "$plan_base") ${waits:--} ? $list_complete"$'\n'
|
|
4372
|
+
readings+="$(branch_readings "$br" "$deferred" "$plan_base") ${waits:--} ? $list_complete ?"$'\n'
|
|
4367
4373
|
# Read from the readings line just built rather than from the variable: the
|
|
4368
4374
|
# subshell `branch_readings` runs in cannot export it back.
|
|
4369
4375
|
# FIELD 6 is the host's own word, in both arms of `branch_readings`:
|
|
@@ -4417,7 +4423,7 @@ for plan in "${plans[@]}"; do
|
|
|
4417
4423
|
while IFS= read -r rd_line; do
|
|
4418
4424
|
[ -n "$rd_line" ] || continue
|
|
4419
4425
|
answer_i=$((answer_i + 1))
|
|
4420
|
-
IFS=$'\t' read -r _st needs \
|
|
4426
|
+
IFS=$'\t' read -r _st needs _ \
|
|
4421
4427
|
<<< "$(printf '%s\n' "$branch_answers" | sed -n "${answer_i}p")"
|
|
4422
4428
|
waits_br=$(printf '%s' "$rd_line" | cut -f9)
|
|
4423
4429
|
if [ "$needs" = "1" ] && [ "$waits_br" != "-" ]; then
|
|
@@ -4428,15 +4434,21 @@ for plan in "${plans[@]}"; do
|
|
|
4428
4434
|
# run, never one per pass.
|
|
4429
4435
|
#
|
|
4430
4436
|
# FIELD 9 IS A LIST, comma-joined, so field 10 answers each prerequisite
|
|
4431
|
-
# IN THE SAME ORDER — one `
|
|
4437
|
+
# IN THE SAME ORDER — one `host_pr_state … --ask` call per name, joined the same
|
|
4432
4438
|
# way. `entry/branch-state.ts` reads the two columns as parallel lists of
|
|
4433
4439
|
# equal length and throws otherwise, so a single answer for several names
|
|
4434
|
-
# would desync them. Field 11 is carried, unreplaced.
|
|
4435
|
-
|
|
4440
|
+
# would desync them. Field 11 is carried, unreplaced. Field 12 says, in
|
|
4441
|
+
# the same order, whether `SLICE_NAMES` holds each name.
|
|
4442
|
+
#
|
|
4443
|
+
# A NAMED SLICE WITH NO REF IS STILL ASKED. Its ref may be gone because it
|
|
4444
|
+
# merged and `plot-release-refs.sh` reaped it, and only the host's MERGED
|
|
4445
|
+
# clears the wait; skipping the call would hold the branch forever.
|
|
4446
|
+
waits_state="" waits_named=""
|
|
4436
4447
|
for _wn in ${waits_br//,/ }; do
|
|
4437
|
-
waits_state+="${waits_state:+,}$(
|
|
4448
|
+
waits_state+="${waits_state:+,}$(host_pr_state "$_wn" --ask)"
|
|
4449
|
+
case "$SLICE_NAMES" in *$'\n'"$_wn"$'\n'*) waits_named+="${waits_named:+,}true" ;; *) waits_named+="${waits_named:+,}false" ;; esac
|
|
4438
4450
|
done
|
|
4439
|
-
refill+="$(printf '%s' "$rd_line" | cut -f1-9) $waits_state $(printf '%s' "$rd_line" | cut -f11)"$'\n'
|
|
4451
|
+
refill+="$(printf '%s' "$rd_line" | cut -f1-9) $waits_state $(printf '%s' "$rd_line" | cut -f11) $waits_named"$'\n'
|
|
4440
4452
|
else
|
|
4441
4453
|
refill+="$rd_line"$'\n'
|
|
4442
4454
|
fi
|
|
@@ -4454,13 +4466,14 @@ for plan in "${plans[@]}"; do
|
|
|
4454
4466
|
# record is re-read by two more `read` loops below, and an EMPTY middle
|
|
4455
4467
|
# column collapses its tab into its neighbour's and shifts every later
|
|
4456
4468
|
# field left. `$claim` is the only field allowed to be last and optional.
|
|
4469
|
+
# `$own` is the branch's state before its prerequisite, the third answer column.
|
|
4457
4470
|
states=""
|
|
4458
4471
|
answer_i=0
|
|
4459
4472
|
while IFS=$'\t' read -r idx br deferred why waits wname claim; do
|
|
4460
4473
|
[ -n "$br" ] || continue
|
|
4461
4474
|
answer_i=$((answer_i + 1))
|
|
4462
|
-
st
|
|
4463
|
-
states+="$idx $br $st $deferred $why $waits $wname $claim"$'\n'
|
|
4475
|
+
IFS=$'\t' read -r st _ own <<< "$(printf '%s\n' "$branch_answers" | sed -n "${answer_i}p")"
|
|
4476
|
+
states+="$idx $br $st $deferred $why $waits $wname ${own:-$st} $claim"$'\n'
|
|
4464
4477
|
done <<< "$order"
|
|
4465
4478
|
|
|
4466
4479
|
# PASS 1e: A DELIVERY CANDIDATE ASKS THE HOST about its subject-proven branches.
|
|
@@ -4519,7 +4532,7 @@ for plan in "${plans[@]}"; do
|
|
|
4519
4532
|
outstanding=0
|
|
4520
4533
|
_loose_degraded_branches=""
|
|
4521
4534
|
wave_states=""
|
|
4522
|
-
while IFS=$'\t' read -r idx br st deferred why waits nm claim; do
|
|
4535
|
+
while IFS=$'\t' read -r idx br st deferred why waits nm _ claim; do
|
|
4523
4536
|
[ "$idx" = "$wid" ] || continue
|
|
4524
4537
|
# EVERY branch, including the deferred ones, and in the order the render
|
|
4525
4538
|
# loop below will walk them — the claimable flags come back positionally,
|
|
@@ -4618,7 +4631,7 @@ for plan in "${plans[@]}"; do
|
|
|
4618
4631
|
# `deferred` to tell a branch that will never move from one that has not
|
|
4619
4632
|
# moved yet.
|
|
4620
4633
|
outlook_branches=""
|
|
4621
|
-
while IFS=$'\t' read -r idx br st deferred why waits nm claim; do
|
|
4634
|
+
while IFS=$'\t' read -r idx br st deferred why waits nm own claim; do
|
|
4622
4635
|
[ "$idx" = "$wid" ] || continue
|
|
4623
4636
|
outlook_branches+="${outlook_branches:+|}$br:$st"
|
|
4624
4637
|
[ "$claim" = "-" ] && claim=""
|
|
@@ -4637,9 +4650,9 @@ for plan in "${plans[@]}"; do
|
|
|
4637
4650
|
# whole of what this state adds over `open`.
|
|
4638
4651
|
waiting) n_waiting=$((n_waiting + 1))
|
|
4639
4652
|
note="waiting on $waits_prose" ;;
|
|
4640
|
-
# A PREREQUISITE NOBODY DECLARED
|
|
4641
|
-
#
|
|
4642
|
-
#
|
|
4653
|
+
# A PREREQUISITE NOBODY DECLARED: no plan names it as a slice, and the
|
|
4654
|
+
# host answered that no PR exists for it. A host that could not be
|
|
4655
|
+
# asked, or a named slice nobody has started, reads `waiting` instead.
|
|
4643
4656
|
blocked) n_prereq_missing=$((n_prereq_missing + 1))
|
|
4644
4657
|
note="blocked — no PR found for $waits_prose" ;;
|
|
4645
4658
|
# The REASON, where the plan recorded one. A bare `deferred` beside a
|
|
@@ -4683,7 +4696,7 @@ for plan in "${plans[@]}"; do
|
|
|
4683
4696
|
# The INTERNAL state ($st), never the prose label ($note): the board
|
|
4684
4697
|
# must not parse a string that exists for humans to read.
|
|
4685
4698
|
json_branches+="${json_branches:+,}{\"branch\":\"$(json_str "$br")\""
|
|
4686
|
-
json_branches+=",\"state\":\"$st\",\"deferred\":$deferred"
|
|
4699
|
+
json_branches+=",\"state\":\"$st\",\"own_state\":\"$own\",\"deferred\":$deferred"
|
|
4687
4700
|
# WHAT PROVED THE LANDING, where the proof is weaker than the host's.
|
|
4688
4701
|
#
|
|
4689
4702
|
# EMITTED ONLY WHERE THE BRANCH READS `merged` AND THE HOST HAS NOT
|