@plot-pm/board 0.16.1 → 0.16.2
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 +101 -96
- package/package.json +2 -1
- package/plot-approve.sh +10 -1
- package/plot-config.sh +10 -14
- package/plot-deliver.sh +10 -1
- package/plot-desk-root.sh +127 -0
- package/plot-dispatch.sh +51 -45
- package/plot-host.sh +84 -26
- package/plot-reap.sh +21 -65
- package/plot-resolve-artifact.sh +13 -19
- package/plot-tmp.sh +62 -14
- package/plot-worker-state.sh +22 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plot-pm/board",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "Local Kanban board for Plot — a glanceable view of plan phases from docs/plans, with sprint and story filters",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"plot-config.sh",
|
|
32
32
|
"plot-default-branch.sh",
|
|
33
33
|
"plot-deliver.sh",
|
|
34
|
+
"plot-desk-root.sh",
|
|
34
35
|
"plot-dispatch.sh",
|
|
35
36
|
"plot-fleet-scan.sh",
|
|
36
37
|
"plot-host.sh",
|
package/plot-approve.sh
CHANGED
|
@@ -128,7 +128,13 @@ git rev-parse --git-dir >/dev/null 2>&1 || die "not a git repository — run thi
|
|
|
128
128
|
cfg() { bash "$script_dir/plot-config.sh" get "$1" "$2"; }
|
|
129
129
|
|
|
130
130
|
repo_root=$(git rev-parse --show-toplevel)
|
|
131
|
-
|
|
131
|
+
# The booking worktree goes under the desk root of the MAIN checkout, never of
|
|
132
|
+
# this tree: inside a desk `--show-toplevel` answers the desk, and the default
|
|
133
|
+
# `.worktrees` would resolve beneath it. No fallback — `plot-desk-root.sh`.
|
|
134
|
+
# shellcheck source=plot-desk-root.sh
|
|
135
|
+
. "$script_dir/plot-desk-root.sh"
|
|
136
|
+
main_root=$(plot_repo_root)
|
|
137
|
+
wt_root=$(plot_desk_root "$main_root") || die "cannot resolve where the booking worktree goes (see above)"
|
|
132
138
|
|
|
133
139
|
PLAN_DIR=$(cfg "Plan directory" "docs/plans/")
|
|
134
140
|
ACTIVE_DIR=$(cfg "Active index" "docs/plans/active/")
|
|
@@ -735,6 +741,9 @@ else
|
|
|
735
741
|
|
|
736
742
|
bookbr="plot/approve-$slug"
|
|
737
743
|
tmpwt="$wt_root/.plot-approve-$slug.$$"
|
|
744
|
+
# `git worktree add` creates the desk root when it is missing, so exclude it
|
|
745
|
+
# first: a booking run must not leave `.worktrees/` untracked in `git status`.
|
|
746
|
+
plot_exclude_desk_root "$main_root"
|
|
738
747
|
# -B: a leftover branch from an earlier failed run must not block this one.
|
|
739
748
|
# It is disposable by construction — created here, pushed, deleted.
|
|
740
749
|
git worktree add -q -B "$bookbr" "$tmpwt" "origin/$MAIN" 2>/dev/null \
|
package/plot-config.sh
CHANGED
|
@@ -27,12 +27,7 @@
|
|
|
27
27
|
# Project board | Branch prefixes | Plan directory | Active index |
|
|
28
28
|
# Delivered index | Sprint directory | Story directory | Story index |
|
|
29
29
|
# Plan template | Worker prompt template | Main branch | Board command
|
|
30
|
-
# Worktree root
|
|
31
|
-
# plot-dispatch.sh; default is the repo's PARENT, which
|
|
32
|
-
# scatters `plot-wt-*` beside the checkout. An absolute
|
|
33
|
-
# path is taken as given; a relative one resolves against
|
|
34
|
-
# the repo root, so `.worktrees` gathers them inside it.
|
|
35
|
-
# The default is kept for repos that never set it.
|
|
30
|
+
# Worktree root the desk root; see its entry under the agent keys below.
|
|
36
31
|
# Worker bound seconds a single prompt run may take in the worker loop
|
|
37
32
|
# before it is ended and the worker exits (no hop). Read by
|
|
38
33
|
# plot-worker-loop.sh; default 3600 (~1h), `0` disables it.
|
|
@@ -99,14 +94,15 @@
|
|
|
99
94
|
# project-owned and reviewed like this one.
|
|
100
95
|
# Absent or empty = no change, so an adopting project that
|
|
101
96
|
# sets nothing behaves exactly as today.
|
|
102
|
-
# Worktree root where /plot-dispatch creates fleet
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
97
|
+
# Worktree root the desk root: where /plot-dispatch creates fleet
|
|
98
|
+
# worktrees and the board writes its action records. A
|
|
99
|
+
# relative value resolves against the MAIN checkout, an
|
|
100
|
+
# absolute one is taken as given. Absent or empty =
|
|
101
|
+
# `<repo>/.worktrees`. The rule is `deskRoot`, asked through
|
|
102
|
+
# `plot-desk-root.sh`; no script resolves it itself. Desks
|
|
103
|
+
# carry no prefix; `plot-wt-*` desks an older dispatch made
|
|
104
|
+
# beside the repo stay there. Every "which worktree holds
|
|
105
|
+
# this branch" read asks `git worktree list` instead.
|
|
110
106
|
# Agent-runner keys (optional; Plot hardcodes no agent tooling, Principle 5):
|
|
111
107
|
# Worker command how /plot-dispatch runs an agent headless on a worktree.
|
|
112
108
|
# /plot-init writes `PLOT_UNATTENDED=1 plot-worker-loop.sh`;
|
package/plot-deliver.sh
CHANGED
|
@@ -81,7 +81,13 @@ git rev-parse --git-dir >/dev/null 2>&1 || die "not a git repository — run thi
|
|
|
81
81
|
cfg() { bash "$script_dir/plot-config.sh" get "$1" "$2"; }
|
|
82
82
|
|
|
83
83
|
repo_root=$(git rev-parse --show-toplevel)
|
|
84
|
-
|
|
84
|
+
# The booking worktree goes under the desk root of the MAIN checkout, never of
|
|
85
|
+
# this tree: inside a desk `--show-toplevel` answers the desk, and the default
|
|
86
|
+
# `.worktrees` would resolve beneath it. No fallback — `plot-desk-root.sh`.
|
|
87
|
+
# shellcheck source=plot-desk-root.sh
|
|
88
|
+
. "$script_dir/plot-desk-root.sh"
|
|
89
|
+
main_root=$(plot_repo_root)
|
|
90
|
+
wt_root=$(plot_desk_root "$main_root") || die "cannot resolve where the booking worktree goes (see above)"
|
|
85
91
|
|
|
86
92
|
PLAN_DIR=$(cfg "Plan directory" "docs/plans/")
|
|
87
93
|
ACTIVE_DIR=$(cfg "Active index" "docs/plans/active/")
|
|
@@ -635,6 +641,9 @@ git fetch -q origin "$MAIN" 2>/dev/null
|
|
|
635
641
|
|
|
636
642
|
bookbr="plot/deliver-$slug"
|
|
637
643
|
tmpwt="$wt_root/.plot-deliver-$slug.$$"
|
|
644
|
+
# `git worktree add` creates the desk root when it is missing, so exclude it
|
|
645
|
+
# first: a booking run must not leave `.worktrees/` untracked in `git status`.
|
|
646
|
+
plot_exclude_desk_root "$main_root"
|
|
638
647
|
# -B: a leftover branch from an earlier failed run must not block this one.
|
|
639
648
|
git worktree add -q -B "$bookbr" "$tmpwt" "origin/$MAIN" 2>/dev/null \
|
|
640
649
|
|| die "could not prepare a booking worktree at $tmpwt.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# The ONE answer to "where do desks and action records go?" — sourced, not run.
|
|
3
|
+
#
|
|
4
|
+
# Every script that resolved this privately asks here instead. Nine sites
|
|
5
|
+
# computed the rule on 2026-10-01 and two of them already answered `.worktrees`
|
|
6
|
+
# while seven answered the checkout's parent, so one of the two populations was
|
|
7
|
+
# always writing somewhere the other did not read.
|
|
8
|
+
#
|
|
9
|
+
# The rule itself is `deskRoot` in the domain, asked through
|
|
10
|
+
# `board/plot-desk-root.mjs`, per *A Shell Script Asks The Domain*: every caller
|
|
11
|
+
# runs once per operator command, which is the cost rule's permitted case.
|
|
12
|
+
#
|
|
13
|
+
# NO CALLER KEEPS A FALLBACK DEFAULT. When the bundle cannot answer — no `node`,
|
|
14
|
+
# a missing file — the caller stops with the reason. A silent fallback is a
|
|
15
|
+
# second default, and two defaults are the defect this removes.
|
|
16
|
+
#
|
|
17
|
+
# plot_repo_root → the MAIN checkout, from anywhere
|
|
18
|
+
# plot_desk_root [repo [configured]] → the desk root; exit 3 when unaskable
|
|
19
|
+
# plot_exclude_desk_root [repo] → keep it out of `git status`; never fails
|
|
20
|
+
#
|
|
21
|
+
# shellcheck shell=bash
|
|
22
|
+
|
|
23
|
+
# The MAIN checkout, from a desk as readily as from the checkout itself.
|
|
24
|
+
#
|
|
25
|
+
# `--show-toplevel` answers the DESK inside a linked worktree, so with
|
|
26
|
+
# `.worktrees` as the default it would resolve `<desk>/.worktrees` and place
|
|
27
|
+
# every tree under a root nothing else reads. `plot-reap.sh:440-447` recorded
|
|
28
|
+
# that failure on 2026-09-10, when every tree read as unplaceable.
|
|
29
|
+
#
|
|
30
|
+
# The parent of `--git-common-dir` is the main checkout from anywhere, because
|
|
31
|
+
# every linked worktree shares that one directory. It is asked FIRST and
|
|
32
|
+
# `--show-toplevel` is the fallback, since the common dir is also correct in a
|
|
33
|
+
# non-worktree checkout: there `.git` is a real directory in the root.
|
|
34
|
+
#
|
|
35
|
+
# PHYSICAL, through `pwd -P`: `git worktree list` and `--show-toplevel` print
|
|
36
|
+
# resolved paths, and a desk root composed from a logical one (`/tmp` against
|
|
37
|
+
# `/private/tmp` on macOS) is a prefix no worktree path ever starts with.
|
|
38
|
+
plot_repo_root() {
|
|
39
|
+
local common root=''
|
|
40
|
+
common=$(git rev-parse --git-common-dir 2>/dev/null) && [ -n "$common" ] && {
|
|
41
|
+
common=$(cd -- "$common" 2>/dev/null && pwd -P) || common=''
|
|
42
|
+
[ -n "$common" ] && root=$(dirname -- "$common")
|
|
43
|
+
}
|
|
44
|
+
[ -n "$root" ] || root=$(git rev-parse --show-toplevel 2>/dev/null) || root=$(pwd)
|
|
45
|
+
printf '%s\n' "$root"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Resolved ONCE at source time, the way `plot-pr-merged.sh:105` resolves its
|
|
49
|
+
# own bundle. Computing it inside a function reads `BASH_SOURCE[0]` at call
|
|
50
|
+
# time, which is the caller's file once the function has been exported.
|
|
51
|
+
_plot_desk_root_mjs="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/board/plot-desk-root.mjs"
|
|
52
|
+
_plot_desk_root_config="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/plot-config.sh"
|
|
53
|
+
|
|
54
|
+
# The desk root for a repository, or exit 3 with the reason on stderr.
|
|
55
|
+
#
|
|
56
|
+
# A second argument is a value the caller was handed in place of the key — a
|
|
57
|
+
# `--worktrees DIR` flag — and goes through the same rule; without one the
|
|
58
|
+
# `Worktree root` key is read.
|
|
59
|
+
#
|
|
60
|
+
# READ THE EXIT CODE. A caller that treats an empty answer as a location would
|
|
61
|
+
# compose paths against the filesystem root.
|
|
62
|
+
plot_desk_root() {
|
|
63
|
+
local repo="${1:-}" bundle="$_plot_desk_root_mjs" configured answer
|
|
64
|
+
[ -n "$repo" ] || repo=$(plot_repo_root)
|
|
65
|
+
if [ ! -f "$bundle" ]; then
|
|
66
|
+
printf 'plot: cannot resolve the desk root: %s is missing\n' "$bundle" >&2
|
|
67
|
+
return 3
|
|
68
|
+
fi
|
|
69
|
+
if [ "$#" -ge 2 ]; then
|
|
70
|
+
configured="$2"
|
|
71
|
+
else
|
|
72
|
+
# Read from the TARGET repository, never the ambient one: `plot-config.sh`
|
|
73
|
+
# walks up from its cwd, so a caller asking about another checkout would
|
|
74
|
+
# otherwise get this one's key.
|
|
75
|
+
# A read that fails is a refusal, never the absent row: reading it as empty
|
|
76
|
+
# would answer the default for a repository that configured another root.
|
|
77
|
+
configured=$(cd -- "$repo" 2>/dev/null && bash "$_plot_desk_root_config" get "Worktree root" "" 2>/dev/null) || {
|
|
78
|
+
printf 'plot: cannot resolve the desk root: the Worktree root key in %s could not be read\n' "$repo" >&2
|
|
79
|
+
return 3
|
|
80
|
+
}
|
|
81
|
+
fi
|
|
82
|
+
answer=$(node "$bundle" "$repo" "$configured" 2>&1) || {
|
|
83
|
+
printf 'plot: cannot resolve the desk root: %s\n' "$answer" >&2
|
|
84
|
+
return 3
|
|
85
|
+
}
|
|
86
|
+
[ -n "$answer" ] || {
|
|
87
|
+
printf 'plot: the desk root rule answered nothing for %s\n' "$repo" >&2
|
|
88
|
+
return 3
|
|
89
|
+
}
|
|
90
|
+
printf '%s\n' "$answer"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Keep the desk root out of `git status`, when it lies inside the repository.
|
|
94
|
+
#
|
|
95
|
+
# The line goes into the COMMON git directory's `info/exclude`: git reads that
|
|
96
|
+
# file from the common directory only, so a linked worktree's private gitdir is
|
|
97
|
+
# the wrong place and a desk writing there would exclude nothing. The write is
|
|
98
|
+
# idempotent, and `info/exclude` is never committed.
|
|
99
|
+
#
|
|
100
|
+
# Called by whoever CREATES the directory. Best-effort and always exit 0: a
|
|
101
|
+
# failure leaves untracked files in a listing, which is untidy, while refusing
|
|
102
|
+
# to create a desk over it would stop the work.
|
|
103
|
+
plot_exclude_desk_root() {
|
|
104
|
+
local repo="${1:-}" bundle="$_plot_desk_root_mjs" line common exclude configured
|
|
105
|
+
[ -n "$repo" ] || repo=$(plot_repo_root)
|
|
106
|
+
[ -f "$bundle" ] || return 0
|
|
107
|
+
configured=$(cd -- "$repo" 2>/dev/null && bash "$_plot_desk_root_config" get "Worktree root" "" 2>/dev/null) || configured=''
|
|
108
|
+
line=$(node "$bundle" --exclude-line "$repo" "$configured" 2>/dev/null) || return 0
|
|
109
|
+
# Empty means the root lies outside the repository, which needs no line.
|
|
110
|
+
[ -n "$line" ] || return 0
|
|
111
|
+
# Already ignored, by a `.gitignore` line /plot-init wrote or by an earlier
|
|
112
|
+
# run. A second rule for a path already ignored is noise in a file people read.
|
|
113
|
+
( cd -- "$repo" 2>/dev/null && git check-ignore -q ".$line" ) && return 0
|
|
114
|
+
common=$(cd -- "$repo" 2>/dev/null && git rev-parse --git-common-dir 2>/dev/null) || return 0
|
|
115
|
+
[ -n "$common" ] || return 0
|
|
116
|
+
case "$common" in /*) ;; *) common="$repo/$common" ;; esac
|
|
117
|
+
exclude="$common/info/exclude"
|
|
118
|
+
if [ -f "$exclude" ] && grep -qxF "$line" "$exclude" 2>/dev/null; then return 0; fi
|
|
119
|
+
mkdir -p "$common/info" 2>/dev/null || return 0
|
|
120
|
+
# A file not ending in a newline would otherwise join this line to its last.
|
|
121
|
+
if [ -s "$exclude" ] && [ "$(tail -c 1 "$exclude" 2>/dev/null)" != "" ]; then
|
|
122
|
+
printf '\n%s\n' "$line" >> "$exclude" 2>/dev/null || true
|
|
123
|
+
else
|
|
124
|
+
printf '%s\n' "$line" >> "$exclude" 2>/dev/null || true
|
|
125
|
+
fi
|
|
126
|
+
return 0
|
|
127
|
+
}
|
package/plot-dispatch.sh
CHANGED
|
@@ -219,40 +219,37 @@ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
|
219
219
|
# WHERE THE WORKTREES LIVE, and by what name
|
|
220
220
|
# ---------------------------------------------------------------------------
|
|
221
221
|
#
|
|
222
|
-
# Two facts
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
222
|
+
# Two facts: the root directory the worktrees sit in, and the prefix their
|
|
223
|
+
# directory names carry. The root is the desk root, `deskRoot`'s answer asked
|
|
224
|
+
# through `plot-desk-root.sh`:
|
|
225
|
+
#
|
|
226
|
+
# `Worktree root:` absent or empty → <main checkout>/.worktrees
|
|
227
|
+
# relative value → <main checkout>/<value>
|
|
228
|
+
# absolute value → <value> as given
|
|
229
|
+
#
|
|
230
|
+
# The prefix is always empty. `plot-wt-` made Plot's worktrees identifiable
|
|
231
|
+
# among unrelated directories in the checkout's parent; the desk root is a
|
|
232
|
+
# directory Plot owns, so the directory already says what they are. Desks a
|
|
233
|
+
# dispatch created under the old default keep their `plot-wt-` names and their
|
|
234
|
+
# place: every read of "which worktree holds this branch" asks `git worktree
|
|
235
|
+
# list`, and `plot-reap.sh` still recognises the legacy path.
|
|
236
|
+
#
|
|
237
|
+
# The root is the MAIN checkout's, wherever this runs: inside a desk,
|
|
238
|
+
# `--show-toplevel` answers the desk and `.worktrees` would resolve beneath it.
|
|
239
|
+
# There is NO FALLBACK: when the rule cannot be asked, dispatch stops with the
|
|
240
|
+
# reason rather than compute a second default.
|
|
234
241
|
#
|
|
235
242
|
# THIS FUNCTION ONLY COMPOSES A ROOT AND A PREFIX. It is the CREATION side. Every
|
|
236
243
|
# read of "which worktree holds this branch" asks `git worktree list` instead —
|
|
237
244
|
# see THE HELD-BRANCH GATE. A second naming convention gives path-guessing a
|
|
238
245
|
# second way to be wrong, so path-guessing is confined to creation alone.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return
|
|
247
|
-
fi
|
|
248
|
-
case "$configured" in
|
|
249
|
-
/*) wt_root="$configured" ;; # absolute: taken as given
|
|
250
|
-
*) wt_root="$rr/$configured" ;; # relative: against the repo root
|
|
251
|
-
esac
|
|
252
|
-
# Normalise away a trailing slash so composed paths never double it. The
|
|
253
|
-
# directory may not exist yet (created on first dispatch), so this is pure
|
|
254
|
-
# string work, not a `cd`.
|
|
255
|
-
wt_root="${wt_root%/}"
|
|
246
|
+
# shellcheck source=plot-desk-root.sh
|
|
247
|
+
. "$script_dir/plot-desk-root.sh"
|
|
248
|
+
resolve_wt_root() { # sets globals wt_root, wt_prefix; exits 3 when unaskable
|
|
249
|
+
wt_root=$(plot_desk_root "$(plot_repo_root)") || {
|
|
250
|
+
echo "plot-dispatch: cannot resolve where the worktrees go — nothing was dispatched." >&2
|
|
251
|
+
exit 3
|
|
252
|
+
}
|
|
256
253
|
wt_prefix=""
|
|
257
254
|
}
|
|
258
255
|
|
|
@@ -1645,9 +1642,13 @@ charter_file_for() { # $1 = agent name → prints the path it would read
|
|
|
1645
1642
|
# inspectable and stoppable even if the plan was since delivered or rejected.
|
|
1646
1643
|
# Refusing to show a running worker because of a phase change would strand it.
|
|
1647
1644
|
repo_root_early=$(git rev-parse --show-toplevel)
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1645
|
+
# Only the two verbs that enumerate desks ask for the root here; every other
|
|
1646
|
+
# path asks where it composes one, so its own refusals answer first.
|
|
1647
|
+
if [ "$mode" = status ] || [ "$mode" = stop ]; then
|
|
1648
|
+
resolve_wt_root
|
|
1649
|
+
wt_root_early="$wt_root"
|
|
1650
|
+
wt_prefix_early="$wt_prefix"
|
|
1651
|
+
fi
|
|
1651
1652
|
|
|
1652
1653
|
# States: "running <pid>" | "finished <pid>" | "waiting <pid> (answer it)"
|
|
1653
1654
|
# | "stalled <pid> (work unfinished)" | "failed <pid> (exit N)"
|
|
@@ -2160,7 +2161,6 @@ if [ "$mode" = "start" ]; then
|
|
|
2160
2161
|
# about any plan, so there is no plan whose phase could refuse it. A gate on a
|
|
2161
2162
|
# slug this verb never takes would refuse every call.
|
|
2162
2163
|
repo_root="$repo_root_early"
|
|
2163
|
-
resolve_wt_root "$repo_root"
|
|
2164
2164
|
|
|
2165
2165
|
# THE DEFAULT BRANCH, by the same three steps the fan-out takes below — the
|
|
2166
2166
|
# config key, then origin's own HEAD, then `main`. Resolved here because the
|
|
@@ -2370,6 +2370,9 @@ EOF
|
|
|
2370
2370
|
# `wait_for_work` skips the outlook scan for an agent that holds none.
|
|
2371
2371
|
slug=""
|
|
2372
2372
|
|
|
2373
|
+
# Where the desks go, asked only now: every refusal above answers first, and
|
|
2374
|
+
# a broken runtime is named by the start-command check rather than here.
|
|
2375
|
+
resolve_wt_root
|
|
2373
2376
|
start_made=0
|
|
2374
2377
|
start_i=0
|
|
2375
2378
|
while [ "$start_i" -lt "$start_n" ]; do
|
|
@@ -2423,6 +2426,9 @@ EOF
|
|
|
2423
2426
|
continue
|
|
2424
2427
|
fi
|
|
2425
2428
|
mkdir -p "$wt_root" 2>/dev/null || true
|
|
2429
|
+
# The desk root lies inside the repository by default, so keep it out of
|
|
2430
|
+
# `git status` the moment it exists.
|
|
2431
|
+
plot_exclude_desk_root "$(plot_repo_root)"
|
|
2426
2432
|
if ! git worktree add -q --detach "$start_wt" "origin/$start_main" 2>/dev/null; then
|
|
2427
2433
|
# NO REMOTE REF IS NOT A FAILURE OF THIS VERB. A fresh clone or a repo
|
|
2428
2434
|
# with no remote has no `origin/<main>`; the local one is the same commit
|
|
@@ -2438,10 +2444,7 @@ EOF
|
|
|
2438
2444
|
# the marker is ignored via `info/exclude` rather than `.gitignore`, because
|
|
2439
2445
|
# an untracked file in a desk reads as unlanded work to
|
|
2440
2446
|
# `plot-worker-state.sh` and would make every free agent look stalled.
|
|
2441
|
-
|
|
2442
|
-
if [ -f "$_excl" ] && ! grep -qxF '.metadata_never_index' "$_excl" 2>/dev/null; then
|
|
2443
|
-
printf '%s\n' '.metadata_never_index' >> "$_excl" 2>/dev/null || true
|
|
2444
|
-
fi
|
|
2447
|
+
plot_desk_exclude "$start_wt" '.metadata_never_index'
|
|
2445
2448
|
: > "$start_wt/.metadata_never_index" 2>/dev/null || true
|
|
2446
2449
|
|
|
2447
2450
|
echo " desk $start_wt (detached at origin/$start_main)"
|
|
@@ -2484,8 +2487,9 @@ if [ "$mode" = "migrate" ]; then
|
|
|
2484
2487
|
configured_root=$("$script_dir/plot-config.sh" get "Worktree root" "")
|
|
2485
2488
|
if [ -z "$configured_root" ]; then
|
|
2486
2489
|
echo "plot-dispatch --migrate: no 'Worktree root:' configured — nothing to migrate."
|
|
2487
|
-
echo " When Worktree root is absent, worktrees
|
|
2488
|
-
echo "
|
|
2490
|
+
echo " When Worktree root is absent, new worktrees go to <repo>/.worktrees;"
|
|
2491
|
+
echo " desks an older dispatch made beside the repo (plot-wt-*) stay there."
|
|
2492
|
+
echo " To move those, first add a 'Worktree root:' key to ## Plot Config."
|
|
2489
2493
|
exit 0
|
|
2490
2494
|
fi
|
|
2491
2495
|
|
|
@@ -3042,13 +3046,12 @@ run_waits_preflight() { # → prints refusals; fills waits_held, adds to n_skipp
|
|
|
3042
3046
|
}
|
|
3043
3047
|
|
|
3044
3048
|
# Where the worktrees live and what their names carry — see resolve_wt_root.
|
|
3045
|
-
# The default is
|
|
3046
|
-
# key relocates them
|
|
3047
|
-
#
|
|
3048
|
-
#
|
|
3049
|
-
# outside the repo.
|
|
3049
|
+
# The default is `<main checkout>/.worktrees` with no prefix; a `Worktree root:`
|
|
3050
|
+
# key relocates them. A root inside the repository is kept out of `git status`
|
|
3051
|
+
# by the `info/exclude` line `plot_exclude_desk_root` writes when a desk is
|
|
3052
|
+
# created.
|
|
3050
3053
|
repo_root=$(git rev-parse --show-toplevel)
|
|
3051
|
-
resolve_wt_root
|
|
3054
|
+
resolve_wt_root
|
|
3052
3055
|
|
|
3053
3056
|
n_dispatched=0 n_reused=0 n_skipped=0 n_started=0
|
|
3054
3057
|
n_brief_asked=0
|
|
@@ -3289,6 +3292,9 @@ write_started_record() { # $@ = branches
|
|
|
3289
3292
|
# stale tip and the push would be a guaranteed non-fast-forward.
|
|
3290
3293
|
git fetch -q origin "$MAIN" 2>/dev/null
|
|
3291
3294
|
|
|
3295
|
+
# `git worktree add` creates the desk root when it is missing; exclude it
|
|
3296
|
+
# first so the booking leaves no untracked `.worktrees/` behind.
|
|
3297
|
+
plot_exclude_desk_root "$(plot_repo_root)"
|
|
3292
3298
|
# -B: a leftover branch from an earlier failed booking must not block this
|
|
3293
3299
|
# one. It is disposable by construction — created here, pushed, deleted.
|
|
3294
3300
|
if ! git worktree add -q -B "$bookbr" "$tmpwt" "origin/$MAIN" 2>/dev/null; then
|
package/plot-host.sh
CHANGED
|
@@ -247,7 +247,9 @@
|
|
|
247
247
|
# list` (no --json), pinned to bb 0.6.0. EXIT 4
|
|
248
248
|
# narrows rather than disappears: it is the
|
|
249
249
|
# tracker-DISABLED case (bb answers 404/410),
|
|
250
|
-
#
|
|
250
|
+
# and a bb whose `--help` lists no `issue`
|
|
251
|
+
# command (Quatico bb), both of
|
|
252
|
+
# which stay *this host cannot answer* where an
|
|
251
253
|
# empty list would say *there are none*. A call
|
|
252
254
|
# that failed on an enabled tracker, or any error
|
|
253
255
|
# wording this adapter does not recognise, exits
|
|
@@ -281,7 +283,8 @@
|
|
|
281
283
|
# Same three outcomes as issue-list, same codes:
|
|
282
284
|
# BITBUCKET NOW ANSWERS via `bb issue view`
|
|
283
285
|
# (pinned to 0.6.0); `url` comes from the view's
|
|
284
|
-
# footer. EXIT 4 is the tracker-DISABLED case
|
|
286
|
+
# footer. EXIT 4 is the tracker-DISABLED case
|
|
287
|
+
# or a bb with no `issue` command,
|
|
285
288
|
# EXIT 3 a lookup that failed or an unrecognised
|
|
286
289
|
# error. An issue that does not exist is a
|
|
287
290
|
# FAILURE here, not an empty body: the caller
|
|
@@ -1016,7 +1019,7 @@ bb_state_listing() { # global bb args… --state <s> --json → one JSON array
|
|
|
1016
1019
|
esac
|
|
1017
1020
|
done
|
|
1018
1021
|
[ -n "$_st" ] || die "bb_state_listing: no --state"
|
|
1019
|
-
_out="$(bb ${_args[@]+"${_args[@]}"} api "/repositories/{ws}/{repo}/pullrequests?state=$(bb_query_state "$_st")&pagelen
|
|
1022
|
+
_out="$(bb ${_args[@]+"${_args[@]}"} api "/repositories/{ws}/{repo}/pullrequests?state=$(bb_query_state "$_st")&pagelen=$BB_LIST_PAGELEN")" || return $?
|
|
1020
1023
|
printf '%s' "$_out" | jq -c '.values // []'
|
|
1021
1024
|
}
|
|
1022
1025
|
|
|
@@ -2033,17 +2036,41 @@ bb_issue_exit_code() {
|
|
|
2033
2036
|
# fails LOUDLY rather than silently mis-reading a column that may have moved.
|
|
2034
2037
|
# `PLOT_BB_SKIP_VERSION_CHECK` exists for the test harness, whose stub bb has no
|
|
2035
2038
|
# meaningful version — the parse is exercised against captured fixture text.
|
|
2039
|
+
#
|
|
2040
|
+
# A VERSION MISMATCH IS NOT ALWAYS A MOVED FORMAT. Two products share the name
|
|
2041
|
+
# `bb` (see the capability check below), and Quatico `bb` — 1.9.0 measured
|
|
2042
|
+
# 2026-10-01 — has no `issue` command at all: `bb --help` lists `pr …`,
|
|
2043
|
+
# `source …` and `api`. Telling it to update would suggest a fix that does not
|
|
2044
|
+
# exist, so a mismatch first asks `bb --help` what the CLI offers. A help text
|
|
2045
|
+
# that lists a `pr` command and no `issue` command is a bb that cannot be asked
|
|
2046
|
+
# about issues, which is exit 4 — the same answer as a disabled tracker. Any
|
|
2047
|
+
# other help text keeps the refusal below, exit 3. Exits 4 or 3; 0 passes.
|
|
2036
2048
|
bb_assert_issue_version() {
|
|
2037
2049
|
[ -n "${PLOT_BB_SKIP_VERSION_CHECK:-}" ] && return 0
|
|
2038
2050
|
local v
|
|
2039
2051
|
v="$(bb --version 2>/dev/null | bb_strip_ansi | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
|
|
2040
2052
|
if [ "$v" != "$BB_ISSUE_VERSION" ]; then
|
|
2053
|
+
if bb_lacks_issue_command; then
|
|
2054
|
+
echo "plot-host: bb ${v:-unknown} lists no issue command — Quatico bb has none, unlike craftamap/bb — so this repository's issues cannot be read through it; declare the issue tracker with the \`Tracker\` config key" >&2
|
|
2055
|
+
return 4
|
|
2056
|
+
fi
|
|
2041
2057
|
echo "plot-host: bb issue parse targets $BB_ISSUE_VERSION but found '${v:-unknown}' — refusing to mis-read a format that may have moved" >&2
|
|
2042
2058
|
return 3
|
|
2043
2059
|
fi
|
|
2044
2060
|
return 0
|
|
2045
2061
|
}
|
|
2046
2062
|
|
|
2063
|
+
# Whether `bb --help` lists commands and `issue` is not one of them. A help
|
|
2064
|
+
# that lists no `pr` command either is not a command listing this can read, so
|
|
2065
|
+
# it answers *not proven* (1) and the caller keeps its exit-3 refusal: guessing
|
|
2066
|
+
# 4 from unreadable help would turn a broken bb into *no issue tracker*.
|
|
2067
|
+
bb_lacks_issue_command() {
|
|
2068
|
+
local help
|
|
2069
|
+
help="$(bb --help 2>&1 | bb_strip_ansi)" || return 1
|
|
2070
|
+
grep -qE '^[[:space:]]+pr([[:space:]]|$)' <<<"$help" || return 1
|
|
2071
|
+
! grep -qE '^[[:space:]]+issues?([[:space:]]|$)' <<<"$help"
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2047
2074
|
# --- bb capability check (--json support) ------------------------------------
|
|
2048
2075
|
#
|
|
2049
2076
|
# TWO TOOLS SHARE THE NAME `bb`. craftamap/bb is a Go binary that does NOT
|
|
@@ -2516,27 +2543,36 @@ jira_check() {
|
|
|
2516
2543
|
# `bb` returned 50 merged PRs (ids 836→787) against a repo numbering to 836, so
|
|
2517
2544
|
# ~780 older merged PRs were invisible to the join.
|
|
2518
2545
|
#
|
|
2519
|
-
# THE
|
|
2520
|
-
#
|
|
2521
|
-
#
|
|
2546
|
+
# THE RULE IS `rules/listing-page.ts`'s `pagePossiblyTruncated`, and this is
|
|
2547
|
+
# its shell copy. `pr-list` runs on every board refresh, so the shell keeps the
|
|
2548
|
+
# rule rather than asking a bundle (docs/shell-and-domain.md), and
|
|
2549
|
+
# `packages/domain/corpus/listing-page.corpus.test.ts` holds the pair:
|
|
2522
2550
|
#
|
|
2523
2551
|
# github (HONOURS --limit) : a state is possibly truncated when it returned
|
|
2524
|
-
# AT LEAST the requested limit
|
|
2525
|
-
#
|
|
2526
|
-
#
|
|
2527
|
-
#
|
|
2528
|
-
#
|
|
2529
|
-
#
|
|
2530
|
-
#
|
|
2531
|
-
#
|
|
2532
|
-
#
|
|
2533
|
-
#
|
|
2534
|
-
#
|
|
2535
|
-
#
|
|
2536
|
-
#
|
|
2537
|
-
#
|
|
2538
|
-
#
|
|
2539
|
-
#
|
|
2552
|
+
# AT LEAST the requested limit. Fewer rows than
|
|
2553
|
+
# the limit PROVES completeness.
|
|
2554
|
+
# bitbucket (ONE FIXED PAGE): the listing ignores --limit and returns one page
|
|
2555
|
+
# per state. A page SHORTER than the page length
|
|
2556
|
+
# is the last page, so it is complete. A page AT
|
|
2557
|
+
# the length stays possibly truncated.
|
|
2558
|
+
#
|
|
2559
|
+
# THE PAGE LENGTH IS A READING, NEVER A CONSTANT THE RULE NAMES. The listing
|
|
2560
|
+
# asks `pagelen=$BB_LIST_PAGELEN` through `bb api`, and the length holds only
|
|
2561
|
+
# where that `bb` passes the query through unchanged, so it is read per version
|
|
2562
|
+
# from `BB_LIST_PAGE_VERSIONS` — pinned like `BB_ISSUE_VERSION`. A version
|
|
2563
|
+
# nobody measured has no length, and every non-empty page under it stays
|
|
2564
|
+
# possibly truncated: the rule never calls a page complete it cannot prove. A
|
|
2565
|
+
# future length of 100 is a new table entry, never a page of 50 read as short.
|
|
2566
|
+
#
|
|
2567
|
+
# Measured on `quatico/quaweb-website`: bb 1.9.0 returned 50 merged rows in one
|
|
2568
|
+
# request on 2026-09-30, and on 2026-10-01 answered `pagelen: 50` with 50 rows,
|
|
2569
|
+
# a `next` and `size: 895`. Before
|
|
2570
|
+
# 2026-10-01 every non-empty Bitbucket page was reported, so 20 open PRs read as
|
|
2571
|
+
# truncated on every refresh (#1137).
|
|
2572
|
+
#
|
|
2573
|
+
# THE PER-BRANCH SWEEP AND THE WINDOWED LISTING MAKE NO PAGE CLAIM. The sweep
|
|
2574
|
+
# proves completeness per branch and the window refuses a short read, so this
|
|
2575
|
+
# detector is not asked about either (`pr_list_states`).
|
|
2540
2576
|
#
|
|
2541
2577
|
# No --limit was requested → the caller accepted the host's default page and is
|
|
2542
2578
|
# owed no report, so no existing no-limit caller's behaviour changes.
|
|
@@ -2555,18 +2591,40 @@ jira_check() {
|
|
|
2555
2591
|
# future diff that teaches the scan to fall back, without moving the failure
|
|
2556
2592
|
# into a minutes-long pulse. See the plan's Done-when item 3.
|
|
2557
2593
|
#
|
|
2594
|
+
# The page length Bitbucket's plain listing asks for. `bb_state_listing` sends
|
|
2595
|
+
# it, and `pr_list_report_truncation` reads it as the page length only for a
|
|
2596
|
+
# `bb` in BB_LIST_PAGE_VERSIONS.
|
|
2597
|
+
BB_LIST_PAGELEN=50
|
|
2598
|
+
|
|
2599
|
+
# The `bb` versions measured to pass `pagelen` through `bb api` unchanged,
|
|
2600
|
+
# space-separated. `adapters/host/listing-paging.ts` holds the same table.
|
|
2601
|
+
BB_LIST_PAGE_VERSIONS="1.9.0"
|
|
2602
|
+
|
|
2603
|
+
# The page length the answering `bb` lists by, or nothing where its version is
|
|
2604
|
+
# not in BB_LIST_PAGE_VERSIONS. Reads the version `bb_require_json` recorded,
|
|
2605
|
+
# so it costs no `bb` call; a skipped capability check records no version.
|
|
2606
|
+
bb_list_page_length() {
|
|
2607
|
+
local v="${BB_CAP_IDENTITY#*/}"
|
|
2608
|
+
case " $BB_LIST_PAGE_VERSIONS " in
|
|
2609
|
+
*" $v "*) printf '%s\n' "$BB_LIST_PAGELEN" ;;
|
|
2610
|
+
esac
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2558
2613
|
# $1 backend $2 requested limit (may be "") $3 state word $4 row count
|
|
2559
2614
|
pr_list_report_truncation() {
|
|
2560
|
-
local be="$1" limit="$2" state="$3" count="$4"
|
|
2615
|
+
local be="$1" limit="$2" state="$3" count="$4" len=""
|
|
2561
2616
|
[ -n "$limit" ] || return 0 # no --limit → no completeness claim owed
|
|
2562
2617
|
[ "$count" -gt 0 ] 2>/dev/null || return 0 # an empty page had nothing to hide
|
|
2563
2618
|
if [ "$be" = "github" ]; then
|
|
2564
2619
|
# github honours the limit: complete unless the page came back AT the limit.
|
|
2565
2620
|
[ "$count" -ge "$limit" ] 2>/dev/null || return 0
|
|
2621
|
+
else
|
|
2622
|
+
# A fixed page: complete below a measured page length, unprovable otherwise.
|
|
2623
|
+
[ "$be" = "bitbucket" ] && len="$(bb_list_page_length)"
|
|
2624
|
+
if [ -n "$len" ] && [ "$count" -lt "$len" ] 2>/dev/null; then return 0; fi
|
|
2566
2625
|
fi
|
|
2567
|
-
#
|
|
2568
|
-
#
|
|
2569
|
-
# the states that were capped, not a whole-call flag that over-reports.
|
|
2626
|
+
# Named per state so a caller can resolve exactly the states that were
|
|
2627
|
+
# capped, not a whole-call flag that over-reports.
|
|
2570
2628
|
echo "plot-host: $be pr-list state=$state possibly truncated ($count rows, requested limit $limit unprovable) — a join against this page may read older branches as 'no PR' (#333)" >&2
|
|
2571
2629
|
}
|
|
2572
2630
|
|