@plot-pm/board 0.17.0 → 0.18.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 +189 -179
- package/package.json +1 -1
- package/plot-agent-manifest.sh +9 -9
- package/plot-config.sh +6 -4
- package/plot-default-branch.sh +15 -19
- package/plot-host.sh +17 -4
package/package.json
CHANGED
package/plot-agent-manifest.sh
CHANGED
|
@@ -48,7 +48,7 @@ manifest_string() { # $1=manifest $2=field → prints the value, or nothing
|
|
|
48
48
|
#
|
|
49
49
|
# WHY THIS EXISTS. `free = process alive AND manifest names no branch`, and the
|
|
50
50
|
# second half was unreachable. `seal_declaration` runs the moment a branch is
|
|
51
|
-
# done;
|
|
51
|
+
# done; the hop that follows runs after `--next` answers and a worktree is
|
|
52
52
|
# built. Between those two points the agent genuinely holds no slice and the
|
|
53
53
|
# manifest still named the last one, so `isFree`'s empty-branch arm — written,
|
|
54
54
|
# exported and unit-tested since `a-dispatch-asks-for-a-free-agent` — had no
|
|
@@ -60,7 +60,7 @@ manifest_string() { # $1=manifest $2=field → prints the value, or nothing
|
|
|
60
60
|
# and the liveness check with it, since both are keyed on the worktree path.
|
|
61
61
|
# `wavesCount` counts hops and no hop has happened yet. The node one-liner
|
|
62
62
|
# round-trips the whole object, so every other field survives verbatim, the same
|
|
63
|
-
# property
|
|
63
|
+
# property the hop's own write records.
|
|
64
64
|
#
|
|
65
65
|
# ADDED, NOT SUBSTITUTED. The hop still rewrites `branch` and `worktree`
|
|
66
66
|
# together; this writes the empty value that sits between two slices. A worker
|
|
@@ -69,7 +69,7 @@ manifest_string() { # $1=manifest $2=field → prints the value, or nothing
|
|
|
69
69
|
#
|
|
70
70
|
# ABSENT IS NOT A FAILURE. No manifest — a hand-started loop, an older
|
|
71
71
|
# dispatcher — means there is nothing to clear and nothing to report, so this
|
|
72
|
-
# returns 0 like
|
|
72
|
+
# returns 0 like the hop's own write does.
|
|
73
73
|
clear_manifest_branch() { # $1=manifest
|
|
74
74
|
local manifest="$1"
|
|
75
75
|
[ -f "$manifest" ] || return 0
|
|
@@ -114,13 +114,13 @@ plot_session_id() {
|
|
|
114
114
|
# into both, and they part at the first hop to a new branch: `session` names the
|
|
115
115
|
# agent and its manifest file for the agent's whole life, while `resumeId` names
|
|
116
116
|
# the current slice's conversation and is what the board joins the transcript
|
|
117
|
-
# on. Reading this field rather than `$PLOT_SESSION_ID` is what makes the
|
|
118
|
-
#
|
|
119
|
-
#
|
|
117
|
+
# on. Reading this field rather than `$PLOT_SESSION_ID` is what makes the hop's
|
|
118
|
+
# write (`writeResumeId` in `manifest-stamp.ts`) mean anything: a reader asks
|
|
119
|
+
# for the handle, and gets the one the hop last wrote.
|
|
120
120
|
#
|
|
121
|
-
# A PARSE FAILURE AND AN ABSENT MANIFEST ARE ONE ANSWER
|
|
122
|
-
#
|
|
123
|
-
#
|
|
121
|
+
# A PARSE FAILURE AND AN ABSENT MANIFEST ARE ONE ANSWER: no handle. A
|
|
122
|
+
# hand-started loop has no manifest, and a manifest nobody can read is not a
|
|
123
|
+
# handle.
|
|
124
124
|
manifest_resume_id() { # $1=manifest → prints the handle, or nothing
|
|
125
125
|
manifest_string "$1" resumeId
|
|
126
126
|
}
|
package/plot-config.sh
CHANGED
|
@@ -150,10 +150,12 @@
|
|
|
150
150
|
# Agent max turns turns one SDK run may take. Absent = 150.
|
|
151
151
|
# Slice max runs SDK runs one slice may start. Absent = 12.
|
|
152
152
|
# Agent context window an SDK run's `autoCompactWindow`. Absent = 200000.
|
|
153
|
-
# Agent max spend dollars one SDK run may spend (`maxBudgetUsd`); the
|
|
154
|
-
#
|
|
155
|
-
# Slice max spend dollars one slice's runs may total, read from the
|
|
156
|
-
#
|
|
153
|
+
# Agent max spend dollars one SDK run may spend (`maxBudgetUsd`); the run
|
|
154
|
+
# then ends `spend-limit`. `$` and `USD` read, e.g. `$20`.
|
|
155
|
+
# Slice max spend dollars one slice's runs may total, read from the slice-
|
|
156
|
+
# spend record before each run. Both: absent/blank = no
|
|
157
|
+
# limit; present and unreadable (0, negative, text) refuses
|
|
158
|
+
# the worker's start.
|
|
157
159
|
# Plot 2 posture keys (repo-declared ceremony bounds; all optional):
|
|
158
160
|
# Plan PRs required | never | optional (never = hard gate)
|
|
159
161
|
# Implementation home this repo | <repo/path list> | none
|
package/plot-default-branch.sh
CHANGED
|
@@ -29,19 +29,12 @@
|
|
|
29
29
|
# now points at, so a second occurrence is visible in a log rather than
|
|
30
30
|
# invisible in a working system.
|
|
31
31
|
#
|
|
32
|
-
# IT
|
|
33
|
-
#
|
|
34
|
-
# somebody's choice, and `--auto` would silently overrule it.
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# repoints `origin/HEAD` at a `plot-corpus-pin` ref it creates in `beforeAll`
|
|
39
|
-
# and restores in `afterAll` — deliberately, so two readings of the estate see
|
|
40
|
-
# one world. While that suite runs the pin RESOLVES, so this repairs nothing and
|
|
41
|
-
# the suite is unaffected. What was measured on 2026-09-04 is the state after a
|
|
42
|
-
# run that never reached its `afterAll`: the symref left behind, the ref it
|
|
43
|
-
# names gone. Fixing that belongs to the suite; this repairs the symptom and
|
|
44
|
-
# reports it loudly enough that the cause stays findable.
|
|
32
|
+
# IT REPAIRS A SYMREF THAT RESOLVES ONLY WHEN IT NAMES `plot-corpus-pin`. A
|
|
33
|
+
# clone whose `origin/HEAD` deliberately names another non-default branch is
|
|
34
|
+
# somebody's choice, and `--auto` would silently overrule it. No operator
|
|
35
|
+
# chooses the pin: `packages/domain/corpus/refs.corpus.test.ts` pins its own
|
|
36
|
+
# disposable clone, so a symref naming it in any other repository is the
|
|
37
|
+
# leftover of an interrupted run.
|
|
45
38
|
|
|
46
39
|
# Whether `refs/remotes/origin/HEAD` names a ref that exists.
|
|
47
40
|
#
|
|
@@ -51,14 +44,17 @@
|
|
|
51
44
|
# Usage: origin_head_resolves [<repo-dir>]
|
|
52
45
|
# Returns: 0 when it resolves — including when there is no symref at all, which
|
|
53
46
|
# is a FRESH CLONE rather than a corruption and has nothing to repair.
|
|
47
|
+
# A symref naming `plot-corpus-pin` is corrupt even when it resolves:
|
|
48
|
+
# that name is left behind only by an interrupted corpus run, never by
|
|
49
|
+
# a live one, since the corpus pins its own clone.
|
|
54
50
|
origin_head_resolves() {
|
|
55
51
|
local dir="${1:-.}" target
|
|
56
52
|
target=$(git -C "$dir" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null) || return 0
|
|
57
|
-
|
|
53
|
+
case "$target" in '') return 0 ;; refs/remotes/origin/plot-corpus-pin) return 1 ;; esac
|
|
58
54
|
git -C "$dir" rev-parse --verify --quiet "${target}^{commit}" >/dev/null 2>&1
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
# Repairs
|
|
57
|
+
# Repairs a `refs/remotes/origin/HEAD` that `origin_head_resolves` refuses, and says what it did.
|
|
62
58
|
#
|
|
63
59
|
# The repair is `git remote set-head origin --auto`, which asks the remote. It
|
|
64
60
|
# is cheap — measured under a second — and it is the same command that fixed
|
|
@@ -70,16 +66,16 @@ origin_head_resolves() {
|
|
|
70
66
|
# this is a self-heal on a path that has its own refusal downstream,
|
|
71
67
|
# so it must never become a second way to stop.
|
|
72
68
|
repair_origin_head() {
|
|
73
|
-
local dir="${1:-.}" was
|
|
69
|
+
local dir="${1:-.}" was why
|
|
74
70
|
origin_head_resolves "$dir" && return 0
|
|
75
71
|
|
|
76
72
|
was=$(git -C "$dir" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null || true)
|
|
73
|
+
why="does not resolve"; [ "$was" != origin/plot-corpus-pin ] || why="is the corpus pin, left by an interrupted corpus run"
|
|
77
74
|
if ! git -C "$dir" remote set-head origin --auto >/dev/null 2>&1; then
|
|
78
|
-
echo "plot: origin/HEAD points at '$was', which
|
|
75
|
+
echo "plot: origin/HEAD points at '$was', which $why, and 'git remote set-head origin --auto' failed — the remote could not be asked." >&2
|
|
79
76
|
return 0
|
|
80
77
|
fi
|
|
81
|
-
now
|
|
82
|
-
echo "plot: repaired origin/HEAD — it pointed at '$was', which does not exist on the remote; it now points at '${now:-<unset>}'." >&2
|
|
78
|
+
echo "plot: repaired origin/HEAD — it pointed at '$was', which $why; it now points at '$(git -C "$dir" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null || echo '<unset>')'." >&2
|
|
83
79
|
return 0
|
|
84
80
|
}
|
|
85
81
|
|
package/plot-host.sh
CHANGED
|
@@ -3605,7 +3605,8 @@ case "$op" in
|
|
|
3605
3605
|
*) die "pr-merged: unknown arg $1" ;;
|
|
3606
3606
|
esac
|
|
3607
3607
|
done
|
|
3608
|
-
|
|
3608
|
+
# NO REMOTE, NO PR: no repository holds one, so nothing merged, whatever gh's auth says.
|
|
3609
|
+
if [ ${#repo_args[@]} -eq 0 ] && [ -z "$(git remote 2>/dev/null)" ]; then echo "not-merged"; elif [ "$be" = "github" ]; then
|
|
3609
3610
|
# --state all, because a merged PR reports CLOSED and the default `open`
|
|
3610
3611
|
# would hide every one of them. --limit 100 rather than 1: the newest PR
|
|
3611
3612
|
# is not the merge, exactly as the state is not the merge.
|
|
@@ -4508,13 +4509,25 @@ case "$op" in
|
|
|
4508
4509
|
# a network failure exits 4 here, the way the Jenkins arm does, so the
|
|
4509
4510
|
# monitor reads *could not ask* and not *no run yet*.
|
|
4510
4511
|
_gh_runs=$(gh run list --branch "$branch" --limit "$limit" \
|
|
4511
|
-
--json headSha,conclusion,status,startedAt,url 2>/dev/null) \
|
|
4512
|
+
--json headSha,conclusion,status,startedAt,url,databaseId 2>/dev/null) \
|
|
4512
4513
|
|| { echo "plot-host: run-for-sha — gh run list failed for '$branch'" >&2; exit 4; }
|
|
4513
|
-
printf '%s' "$_gh_runs" | jq -c --arg sha "$sha" \
|
|
4514
|
+
_gh_match=$(printf '%s' "$_gh_runs" | jq -c --arg sha "$sha" \
|
|
4514
4515
|
'(map(select(.headSha == $sha)) | .[0]) | select(. != null)
|
|
4515
4516
|
| {sha:.headSha, status:.status,
|
|
4516
4517
|
conclusion:(if (.conclusion // "") == "" then null else .conclusion end),
|
|
4517
|
-
url:.url, startedAt:.startedAt}' || exit 4
|
|
4518
|
+
url:.url, startedAt:.startedAt, databaseId:.databaseId}') || exit 4
|
|
4519
|
+
[ -n "$_gh_match" ] || exit 0
|
|
4520
|
+
# ONE MORE CALL, ONLY FOR A CONCLUDED FAILURE (#1295): `gh run view --json
|
|
4521
|
+
# jobs` is the only place a 0-step job (no runner ever picked up the run)
|
|
4522
|
+
# shows up, apart from a real failure. A run still going or that
|
|
4523
|
+
# succeeded costs no extra call.
|
|
4524
|
+
_gh_db_id=$(printf '%s' "$_gh_match" | jq -r 'if (.conclusion == "failure" or .conclusion == "cancelled") then .databaseId else "" end')
|
|
4525
|
+
_gh_match=$(printf '%s' "$_gh_match" | jq -c 'del(.databaseId)') || exit 4
|
|
4526
|
+
[ -z "$_gh_db_id" ] && { printf '%s' "$_gh_match"; exit 0; }
|
|
4527
|
+
# Slurped via stdin, never `--argjson`: that flag caps at 128 KB on Linux.
|
|
4528
|
+
_gh_jobs=$(gh run view "$_gh_db_id" --json jobs 2>/dev/null) || { echo "plot-host: run-for-sha — gh run view failed" >&2; exit 4; }
|
|
4529
|
+
jq -n -c --slurpfile match <(printf '%s' "$_gh_match") --slurpfile run <(printf '%s' "$_gh_jobs") \
|
|
4530
|
+
'$match[0] + {jobs: [$run[0].jobs[] | {conclusion, steps: (.steps | length)}]}' || exit 4
|
|
4518
4531
|
;;
|
|
4519
4532
|
|
|
4520
4533
|
issue-list)
|