@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plot-pm/board",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
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",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"dist/board-server.mjs",
|
|
25
25
|
"plot-agent-monitor.sh",
|
|
26
26
|
"plot-agent-manifest.sh",
|
|
27
|
-
"plot-build-monitor.sh",
|
|
28
27
|
"plot-monitor-subject.sh",
|
|
29
28
|
"plot-approve.sh",
|
|
30
29
|
"plot-budget.sh",
|
|
@@ -41,7 +40,6 @@
|
|
|
41
40
|
"plot-release-refs.sh",
|
|
42
41
|
"plot-state-receipt.sh",
|
|
43
42
|
"plot-tmp.sh",
|
|
44
|
-
"plot-transcript-quiet.sh",
|
|
45
43
|
"plot-worker-state.sh"
|
|
46
44
|
],
|
|
47
45
|
"scripts": {
|
|
@@ -63,6 +61,7 @@
|
|
|
63
61
|
"@types/react": "^19.2.0",
|
|
64
62
|
"@types/react-dom": "^19.2.0",
|
|
65
63
|
"@vitejs/plugin-react": "^6.0.0",
|
|
64
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
66
65
|
"class-variance-authority": "^0.7.1",
|
|
67
66
|
"clsx": "^2.1.1",
|
|
68
67
|
"esbuild": "^0.28.0",
|
|
@@ -75,7 +74,7 @@
|
|
|
75
74
|
"typescript": "^5.9.0",
|
|
76
75
|
"vite": "^8.1.0",
|
|
77
76
|
"vite-plugin-singlefile": "^2.3.0",
|
|
78
|
-
"vitest": "
|
|
77
|
+
"vitest": "4.1.11",
|
|
79
78
|
"zod": "^4.4.0"
|
|
80
79
|
},
|
|
81
80
|
"engines": {
|
package/plot-agent-manifest.sh
CHANGED
|
@@ -43,41 +43,6 @@ manifest_string() { # $1=manifest $2=field → prints the value, or nothing
|
|
|
43
43
|
printf '%s' "$value"
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
# A non-negative integer field, or `0`. The node call itself only ever prints
|
|
47
|
-
# a clean digit string or "0", so a failed call is the one case needing a
|
|
48
|
-
# shell-side fallback.
|
|
49
|
-
manifest_count() { # $1=manifest $2=field → prints a count
|
|
50
|
-
local manifest="$1"
|
|
51
|
-
[ -n "$manifest" ] && [ -f "$manifest" ] || { printf '0'; return 0; }
|
|
52
|
-
node -e '
|
|
53
|
-
const fs = require("fs");
|
|
54
|
-
try {
|
|
55
|
-
const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
56
|
-
const n = manifest[process.argv[2]];
|
|
57
|
-
process.stdout.write(Number.isInteger(n) && n >= 0 ? String(n) : "0");
|
|
58
|
-
} catch { process.stdout.write("0"); }
|
|
59
|
-
' "$manifest" "$2" 2>/dev/null || printf '0'
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
# Raise a non-negative integer field by one, leaving every other field verbatim.
|
|
63
|
-
#
|
|
64
|
-
# THROUGH A TEMP FILE AND A RENAME, the shape every writer here takes: a reader
|
|
65
|
-
# never sees a partial manifest. A write that fails leaves the file alone and
|
|
66
|
-
# reports it; an absent manifest is not a failure.
|
|
67
|
-
raise_manifest_count() { # $1=manifest $2=field
|
|
68
|
-
local manifest="$1" tmp="$1.plot-count-tmp"
|
|
69
|
-
[ -n "$manifest" ] && [ -f "$manifest" ] || return 0
|
|
70
|
-
node -e '
|
|
71
|
-
const fs = require("fs");
|
|
72
|
-
const [file, field, tmp] = process.argv.slice(1);
|
|
73
|
-
const manifest = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
74
|
-
const n = manifest[field];
|
|
75
|
-
manifest[field] = (Number.isInteger(n) && n >= 0 ? n : 0) + 1;
|
|
76
|
-
fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2) + "\n");
|
|
77
|
-
' "$manifest" "$2" "$tmp" 2>/dev/null || { rm -f "$tmp"; return 1; }
|
|
78
|
-
mv -f "$tmp" "$manifest" 2>/dev/null || { rm -f "$tmp"; return 1; }
|
|
79
|
-
}
|
|
80
|
-
|
|
81
46
|
# Clear `branch` when a slice finishes, so the window before the next one is
|
|
82
47
|
# observable.
|
|
83
48
|
#
|
package/plot-config.sh
CHANGED
|
@@ -144,6 +144,16 @@
|
|
|
144
144
|
# it does today and names `no-brief-command` as the
|
|
145
145
|
# reason, so a project that has never set the key behaves
|
|
146
146
|
# exactly as before.
|
|
147
|
+
# Agent runner command | sdk: how the JS loop runs a worker's prompt.
|
|
148
|
+
# Absent = command.
|
|
149
|
+
# Agent models `role = model` list (`worker = sonnet`); a charter wins.
|
|
150
|
+
# Agent max turns turns one SDK run may take. Absent = 150.
|
|
151
|
+
# Slice max runs SDK runs one slice may start. Absent = 12.
|
|
152
|
+
# Agent context window an SDK run's `autoCompactWindow`. Absent = 200000.
|
|
153
|
+
# Agent max spend dollars one SDK run may spend (`maxBudgetUsd`); the
|
|
154
|
+
# run then ends `spend-limit`. Absent = no limit, never 0.
|
|
155
|
+
# Slice max spend dollars one slice's runs may total, read from the
|
|
156
|
+
# slice-spend record before each run. Absent = no limit.
|
|
147
157
|
# Plot 2 posture keys (repo-declared ceremony bounds; all optional):
|
|
148
158
|
# Plan PRs required | never | optional (never = hard gate)
|
|
149
159
|
# Implementation home this repo | <repo/path list> | none
|
|
@@ -202,10 +212,7 @@ cmd="${1:?Usage: plot-config.sh get <key> [default]}"
|
|
|
202
212
|
key="${2:?Usage: plot-config.sh get <key> [default]}"
|
|
203
213
|
default="${3:-}"
|
|
204
214
|
|
|
205
|
-
|
|
206
|
-
echo "plot-config: unknown subcommand '$cmd' (only 'get' is supported)" >&2
|
|
207
|
-
exit 1
|
|
208
|
-
fi
|
|
215
|
+
[ "$cmd" = "get" ] || { echo "plot-config: unknown subcommand '$cmd' (only 'get' is supported)" >&2; exit 1; }
|
|
209
216
|
|
|
210
217
|
# THE CALLER'S ROOT IS TAKEN WHERE IT OFFERS ONE. `git rev-parse` is ~5 ms and
|
|
211
218
|
# this script runs once per config key, so a caller reading several keys pays
|
|
@@ -219,11 +226,8 @@ fi
|
|
|
219
226
|
# exported stale path would otherwise make every config read answer from a
|
|
220
227
|
# repository that is not this one, silently. Asking git is the safe answer and
|
|
221
228
|
# stays the default for every caller that offers nothing.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
else
|
|
225
|
-
root=$(git rev-parse --show-toplevel 2>/dev/null) || root="."
|
|
226
|
-
fi
|
|
229
|
+
root="${PLOT_REPO_ROOT:-}"
|
|
230
|
+
[ -n "$root" ] && [ -d "$root" ] || root=$(git rev-parse --show-toplevel 2>/dev/null) || root="."
|
|
227
231
|
|
|
228
232
|
# Find the first repo-root file that contains a ## Plot Config section.
|
|
229
233
|
# CLAUDE.md wins for backwards compatibility; AGENTS.md is the modern fallback.
|
|
@@ -261,8 +265,4 @@ if [ -n "$config_file" ]; then
|
|
|
261
265
|
s/^[[:space:]]+//; s/[[:space:]]+$//') # trim ends
|
|
262
266
|
fi
|
|
263
267
|
|
|
264
|
-
|
|
265
|
-
printf '%s\n' "$value"
|
|
266
|
-
else
|
|
267
|
-
printf '%s\n' "$default"
|
|
268
|
-
fi
|
|
268
|
+
printf '%s\n' "${value:-$default}"
|
package/plot-dispatch.sh
CHANGED
|
@@ -1374,14 +1374,12 @@ start_worker() {
|
|
|
1374
1374
|
#
|
|
1375
1375
|
# EVERY WORKER IS BORN MONITORED, AND THAT IS ENFORCED HERE OR NOWHERE.
|
|
1376
1376
|
#
|
|
1377
|
-
#
|
|
1378
|
-
# the agent
|
|
1379
|
-
#
|
|
1380
|
-
#
|
|
1381
|
-
#
|
|
1382
|
-
#
|
|
1383
|
-
# (`plot-worker-state.sh`'s `plot_worker_idle_watch_pass`), and the wrapper
|
|
1384
|
-
# itself reports `gone`/`clear` after `wait "$agent"` returns, below.
|
|
1377
|
+
# The AgentMonitor starts INSIDE the wrapper, as its child, immediately
|
|
1378
|
+
# before the agent, watching the desk. The PROCESS is no longer a monitor's
|
|
1379
|
+
# subject: since `bug/the-loop-reports-idle` the loop's own watcher judges `idle`
|
|
1380
|
+
# (`board/plot-worker-loop.mjs`'s `idleVerdict`, built from the domain's
|
|
1381
|
+
# `idleNow`), and the wrapper itself reports `gone`/`clear` after
|
|
1382
|
+
# `wait "$agent"` returns, below.
|
|
1385
1383
|
#
|
|
1386
1384
|
# WHY INSIDE THE WRAPPER RATHER THAN BESIDE IT. The wrapper already outlives
|
|
1387
1385
|
# its agent by construction — it must, or there would be no exit code to
|
|
@@ -1481,14 +1479,8 @@ start_worker() {
|
|
|
1481
1479
|
fi
|
|
1482
1480
|
fi
|
|
1483
1481
|
|
|
1484
|
-
local agent_monitor=''
|
|
1482
|
+
local agent_monitor=''
|
|
1485
1483
|
[ -x "$script_dir/plot-agent-monitor.sh" ] && agent_monitor="$script_dir/plot-agent-monitor.sh"
|
|
1486
|
-
# THE THIRD MONITOR, born the same way and for the same reason. It watches the
|
|
1487
|
-
# RUN — a Build is its own entity in the spec, so a monitor per entity is the
|
|
1488
|
-
# pattern rather than an exception to it. Its cadence is the WorkerMonitor's
|
|
1489
|
-
# 30 s rather than the AgentMonitor's 300 s, and it can afford that against a
|
|
1490
|
-
# HOST because it asks nothing while no run is live.
|
|
1491
|
-
[ -x "$script_dir/plot-build-monitor.sh" ] && build_monitor="$script_dir/plot-build-monitor.sh"
|
|
1492
1484
|
local stamp_now
|
|
1493
1485
|
stamp_now=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1494
1486
|
# THE THREE NAMES THE CHARTER DECLARED, and they travel as env vars for the
|
|
@@ -1563,11 +1555,10 @@ start_worker() {
|
|
|
1563
1555
|
PLOT_MANIFEST_FILE="$manifest_dir/$session.json" \
|
|
1564
1556
|
PLOT_STAMP_STARTED="$stamp_now" \
|
|
1565
1557
|
PLOT_AGENT_MONITOR="$agent_monitor" \
|
|
1566
|
-
PLOT_BUILD_MONITOR="$build_monitor" \
|
|
1567
1558
|
PLOT_EXIT_FILE="$wt/.plot-worker.exit" PLOT_PID_FILE="$wt/.plot-worker.pid" \
|
|
1568
1559
|
PLOT_WRAPPER_PID_FILE="$wt/.plot-worker.wrapper.pid" \
|
|
1569
1560
|
PLOT_SCRIPT_DIR="$script_dir" \
|
|
1570
|
-
exec nohup sh -c 'printf "%s" "$$" > "$PLOT_WRAPPER_PID_FILE"; wmon=""; amon="";
|
|
1561
|
+
exec nohup sh -c 'printf "%s" "$$" > "$PLOT_WRAPPER_PID_FILE"; wmon=""; amon=""; if [ -n "$PLOT_AGENT_MONITOR" ]; then "$PLOT_AGENT_MONITOR" & amon=$!; fi; PATH="$PLOT_SCRIPT_DIR:$PATH"; export PATH; ( '"$cmd"' ) & agent=$!; trap "" TERM; printf "%s" "$agent" > "$PLOT_PID_FILE"; if [ -f "$PLOT_MANIFEST_FILE" ]; then awk -v pid="$agent" -v started="$PLOT_STAMP_STARTED" -v wrapper="$$" -v wmon="$wmon" -v amon="$amon" '"'"'
|
|
1571
1562
|
BEGIN { relaunch = 0; count = 1; stamped = 0 }
|
|
1572
1563
|
FNR == NR {
|
|
1573
1564
|
if ($0 ~ /^ "pid": "[^"]*",$/) {
|
|
@@ -1585,7 +1576,6 @@ start_worker() {
|
|
|
1585
1576
|
print " \"wrapperPid\": \"" wrapper "\","
|
|
1586
1577
|
print " \"workerMonitorPid\": \"" wmon "\","
|
|
1587
1578
|
print " \"agentMonitorPid\": \"" amon "\","
|
|
1588
|
-
print " \"buildMonitorPid\": \"" bmon "\","
|
|
1589
1579
|
if (relaunch) {
|
|
1590
1580
|
print " \"previousPid\": \"" displaced "\","
|
|
1591
1581
|
print " \"relaunches\": " count ","
|
|
@@ -1595,7 +1585,6 @@ start_worker() {
|
|
|
1595
1585
|
$0 ~ /^ "wrapperPid": "[^"]*",$/ { next }
|
|
1596
1586
|
$0 ~ /^ "workerMonitorPid": "[^"]*",$/ { next }
|
|
1597
1587
|
$0 ~ /^ "agentMonitorPid": "[^"]*",$/ { next }
|
|
1598
|
-
$0 ~ /^ "buildMonitorPid": "[^"]*",$/ { next }
|
|
1599
1588
|
relaunch && $0 ~ /^ "previousPid": "[^"]*",$/ { next }
|
|
1600
1589
|
relaunch && $0 ~ /^ "relaunches": [0-9]+,$/ { next }
|
|
1601
1590
|
relaunch && $0 ~ /^ "startedAt": "[^"]*"$/ { print " \"startedAt\": \"" started "\""; next }
|
|
@@ -3043,7 +3032,9 @@ esac
|
|
|
3043
3032
|
# merged PR outlives the branch it was cut from.
|
|
3044
3033
|
#
|
|
3045
3034
|
# `NONE` AND SILENCE ARE DIFFERENT ANSWERS. `NONE` means the host was asked and
|
|
3046
|
-
# has never seen a PR for that branch
|
|
3035
|
+
# has never seen a PR for that branch. For a name in `plot-fleet-scan.sh
|
|
3036
|
+
# --slice-names` — a slice of any live plan, not deferred — that is a slice
|
|
3037
|
+
# nobody started, which is `waiting`; for any other name it is `blocked`. A host that
|
|
3047
3038
|
# could not be asked is neither permission nor proof of a typo, so it HOLDS the
|
|
3048
3039
|
# branch at `waiting`. Both refuse; only one tells the operator to fix the plan.
|
|
3049
3040
|
|
|
@@ -3172,13 +3163,27 @@ declare -a waits_freed=()
|
|
|
3172
3163
|
# already sets: a dry run that offers what a real run would refuse is worse than
|
|
3173
3164
|
# no dry run — it is the same wrong answer with a reassurance attached.
|
|
3174
3165
|
run_waits_preflight() { # → prints refusals; fills waits_held, adds to n_skipped
|
|
3175
|
-
local br prereq answer held
|
|
3166
|
+
local br prereq answer held slice_names=""
|
|
3176
3167
|
while IFS=$'\t' read -r br prereq; do
|
|
3177
3168
|
[ -n "$br" ] || continue
|
|
3178
3169
|
answer=$(prereq_answer "$prereq")
|
|
3179
3170
|
case "$answer" in
|
|
3180
3171
|
merged) continue ;;
|
|
3181
|
-
|
|
3172
|
+
# A slice somebody may still start has no PR until its work starts: a
|
|
3173
|
+
# wait, not a typo (#1305). The set is the scan's, of the whole estate,
|
|
3174
|
+
# so a slice of ANOTHER plan reads as it does on the board. Asked once,
|
|
3175
|
+
# and only when the host answered `none`.
|
|
3176
|
+
# ABSENT IS NOT FALSE. A scan that failed or printed nothing leaves the
|
|
3177
|
+
# set unread, and an unread set holds the branch at `waiting` exactly as
|
|
3178
|
+
# an unaskable host does: telling an operator to fix a correct plan is its
|
|
3179
|
+
# own defect. stderr stays visible, and the refusal names the failure.
|
|
3180
|
+
none) [ -n "$slice_names" ] || slice_names=$'\n'"$("$script_dir/plot-fleet-scan.sh" --slice-names)"$'\n' || slice_names=$'\n\n'
|
|
3181
|
+
case "$slice_names" in
|
|
3182
|
+
$'\n\n') held=waiting
|
|
3183
|
+
echo "plot-fleet-scan.sh --slice-names gave no answer: $prereq has no PR, held as waiting rather than called a typo" ;;
|
|
3184
|
+
*$'\n'"$prereq"$'\n'*) held=waiting ;;
|
|
3185
|
+
*) held=blocked ;;
|
|
3186
|
+
esac ;;
|
|
3182
3187
|
*) held=waiting ;;
|
|
3183
3188
|
esac
|
|
3184
3189
|
# `--allow-waiting` SAYS SO ON THE LINE IT OVERRIDES, ONCE PER PREREQUISITE
|
|
@@ -3267,9 +3272,7 @@ worker_state_field() {
|
|
|
3267
3272
|
#
|
|
3268
3273
|
# `worker=` still travels in the footer. It says how this repo is configured,
|
|
3269
3274
|
# which remains a fact about the repo even where it no longer explains a count.
|
|
3270
|
-
|
|
3271
|
-
echo "summary: dispatched=$1 reused=$2 skipped=$3 started=$4 brief=missing worker=$(worker_state_field) brief_asked=${n_brief_asked:-0}"
|
|
3272
|
-
}
|
|
3275
|
+
# The line is printed once, at the end of the run.
|
|
3273
3276
|
|
|
3274
3277
|
# ---------------------------------------------------------------------------
|
|
3275
3278
|
# Parallel-agents cap: warn and raise when exceeded
|
|
@@ -4225,7 +4228,7 @@ book_started ${claimed_now[@]+"${claimed_now[@]}"} || true
|
|
|
4225
4228
|
# appears before the footer.
|
|
4226
4229
|
check_and_update_cap "$n_started"
|
|
4227
4230
|
|
|
4228
|
-
|
|
4231
|
+
echo "summary: dispatched=$n_dispatched reused=$n_reused skipped=$n_skipped started=$n_started brief=missing worker=$(worker_state_field) brief_asked=${n_brief_asked:-0}"
|
|
4229
4232
|
|
|
4230
4233
|
# THE RECEIPT IS SPENT HERE, on the fan-out COMPLETING — never at the gate.
|
|
4231
4234
|
# `plot-controller-gate.sh` clears on a receipt and LEAVES it, so a run that
|