@erclx/canon 5.14.0 → 5.14.1
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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/review-pr/SKILL.md +1 -1
- package/claude/skills/review-pr/references/close-out.md +1 -1
- package/claude/skills/review-pr/references/post.md +1 -1
- package/claude/skills/role-orchestrator/SKILL.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +10 -10
- package/claude/skills/role-orchestrator/scripts/poll.ts +628 -0
- package/claude/skills/role-orchestrator/scripts/repo.ts +69 -0
- package/claude/skills/role-orchestrator/scripts/watch.ts +267 -0
- package/docs/agents/pr-reads.md +1 -1
- package/docs/agents/sandbox.md +3 -3
- package/docs/agents/state-scoped-risk.md +2 -2
- package/package.json +4 -5
- package/scripts/core/check-color-source.sh +11 -1
- package/scripts/core/install-check.sh +13 -4
- package/scripts/lib/sandbox-dispatch.sh +1 -1
- package/scripts/lib/sandbox-fixtures.sh +2 -2
- package/scripts/lib/sandbox-path.sh +1 -1
- package/scripts/manage-sandbox.sh +2 -2
- package/src/commands/sandbox.ts +2 -2
- package/src/context/citations.ts +1 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/coverage.ts +1 -1
- package/src/sandbox/expect.ts +0 -1
- package/src/shipped/references.ts +8 -10
- package/src/targets/pulls.ts +2 -2
- package/tsconfig.json +1 -1
- package/claude/skills/role-orchestrator/scripts/poll.sh +0 -504
- package/claude/skills/role-orchestrator/scripts/watch.sh +0 -181
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Reports worker and pull request transitions as they happen. Reads only.
|
|
3
|
-
#
|
|
4
|
-
# The poll answers "what moved since the last run" on a schedule the session
|
|
5
|
-
# holds. This answers "what just changed" on a loop of its own, and it exists
|
|
6
|
-
# because the two readings a dispatch needs are not one reading: a worker that
|
|
7
|
-
# finishes goes idle and a worker that crashes vanishes, so a watch matching
|
|
8
|
-
# only the pull request list stays silent through the second. A third case is
|
|
9
|
-
# neither: a worker that stops on a question or a prompt neither finishes nor
|
|
10
|
-
# crashes, and sits reading "waiting" beside the two statuses that resolve on
|
|
11
|
-
# their own until something reports it, which is the WORKER-STOPPED line
|
|
12
|
-
# below, or WORKER-UNMEASURABLE for a "waiting" row too old to carry a stamp
|
|
13
|
-
# this reads.
|
|
14
|
-
#
|
|
15
|
-
# `set -e` is deliberately not set. This runs for hours, and one transient `gh`
|
|
16
|
-
# failure aborting the loop is a silent stop rather than a reported one. Every
|
|
17
|
-
# read below states its own failure instead.
|
|
18
|
-
set -uo pipefail
|
|
19
|
-
|
|
20
|
-
# Seconds between passes. A pass costs two cheap reads and the transitions it
|
|
21
|
-
# watches take minutes, so the number trades staleness against little. Raise it
|
|
22
|
-
# in a project whose workers run long.
|
|
23
|
-
INTERVAL=60
|
|
24
|
-
|
|
25
|
-
# Seconds a worker can sit in "waiting" before this reports it as stopped
|
|
26
|
-
# rather than folding it into the ordinary status-change lines below. "busy"
|
|
27
|
-
# and "idle" resolve on their own; "waiting" does not, so a dwell that keeps
|
|
28
|
-
# growing there is a session blocked on something outside itself.
|
|
29
|
-
#
|
|
30
|
-
# Measured against the live session registry: 341 usable records carried a
|
|
31
|
-
# status at all, and exactly one carried "waiting", too sparse a sample to fit
|
|
32
|
-
# a distribution. The number is picked from the two bounds the measurement can
|
|
33
|
-
# still name rather than from a round guess: well above INTERVAL, so a handful
|
|
34
|
-
# of passes confirm the row before it reports rather than one slow tool call
|
|
35
|
-
# tripping it, and well inside the ten-to-thirty-minute span a dispatched build
|
|
36
|
-
# ordinarily runs, so a real stall is caught with most of that window still
|
|
37
|
-
# open to act on it.
|
|
38
|
-
#
|
|
39
|
-
# That one record carried `waitingFor: "approve Bash"`, a permission prompt
|
|
40
|
-
# rather than a question, and the two clear on different schedules: a prompt
|
|
41
|
-
# resolves the moment a person approves it, where a question can sit
|
|
42
|
-
# legitimately while a controller finishes a turn. This number is defensible
|
|
43
|
-
# for the second and generous for the first, and the gap stays open rather
|
|
44
|
-
# than splitting the constant on a sample of one.
|
|
45
|
-
STALL_THRESHOLD_S=300
|
|
46
|
-
|
|
47
|
-
# Resolving the main worktree root rather than this file's folder keeps a watch
|
|
48
|
-
# started from a linked worktree reading the same repository as one started from
|
|
49
|
-
# main. The repository field on a roster row is that root's `.git`.
|
|
50
|
-
MAIN_ROOT="$(git worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')"
|
|
51
|
-
if [ -z "$MAIN_ROOT" ]; then
|
|
52
|
-
echo "watch: not a git repository, so nothing is watched" >&2
|
|
53
|
-
exit 1
|
|
54
|
-
fi
|
|
55
|
-
REPOSITORY="$MAIN_ROOT/.git"
|
|
56
|
-
|
|
57
|
-
# The base branch is read rather than assumed, since this ships to projects that
|
|
58
|
-
# do not all call it `main`. It is what separates a worker from the controlling
|
|
59
|
-
# session, which sits on the base branch in the main worktree.
|
|
60
|
-
BASE_REF="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null || true)"
|
|
61
|
-
if [ -z "$BASE_REF" ]; then
|
|
62
|
-
BASE_REF="origin/$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name 2>/dev/null || echo main)"
|
|
63
|
-
fi
|
|
64
|
-
BASE_BRANCH="${BASE_REF#origin/}"
|
|
65
|
-
|
|
66
|
-
# Every session in this repository holding a branch other than the base one is a
|
|
67
|
-
# worker, whoever launched it. The prototype matched the `orchestrator-` prefix
|
|
68
|
-
# instead, which reads a dispatched worker and misses every hand-launched one,
|
|
69
|
-
# and those are the ordinary shape whenever the operator is launching.
|
|
70
|
-
#
|
|
71
|
-
# The row goes out tab-separated and comes back read with IFS set to a tab. A
|
|
72
|
-
# name this client actually writes carries spaces, `Update session markdown
|
|
73
|
-
# and check runnable commands (3)` among them, and splitting on whitespace
|
|
74
|
-
# took that one apart into a wrong name, branch, status, and dwell.
|
|
75
|
-
read_workers() {
|
|
76
|
-
canon sessions list --json 2>/dev/null | tail -1 | jq -r \
|
|
77
|
-
--arg repository "$REPOSITORY" --arg base "$BASE_BRANCH" '
|
|
78
|
-
.sessions[]?
|
|
79
|
-
| select(.repository == $repository)
|
|
80
|
-
| select(.branch != null and .branch != $base)
|
|
81
|
-
| [.name, .branch, .status, (.statusDwellMs // -1 | tostring)]
|
|
82
|
-
| @tsv
|
|
83
|
-
' 2>/dev/null | sort
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
read_pulls() {
|
|
87
|
-
gh pr list --state open --json number,headRefName,title --jq \
|
|
88
|
-
'.[] | "PR-OPEN #\(.number) \(.headRefName) \(.title)"' 2>/dev/null | sort
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
# A failed read is reported and the previous baseline kept. Reading an empty
|
|
92
|
-
# result as the current state would report every worker gone and every pull
|
|
93
|
-
# request new on the pass after, which is the one failure that makes a detection
|
|
94
|
-
# tool worth less than no tool.
|
|
95
|
-
#
|
|
96
|
-
# The two sources carry a baseline flag apiece rather than sharing one. A read
|
|
97
|
-
# that fails on the first pass leaves its baseline empty while the other source
|
|
98
|
-
# establishes one, and a single flag would then report that source's whole list
|
|
99
|
-
# as new on the pass after it recovers.
|
|
100
|
-
prev_pulls=""
|
|
101
|
-
prev_workers=""
|
|
102
|
-
prev_names=""
|
|
103
|
-
pulls_seen=0
|
|
104
|
-
workers_seen=0
|
|
105
|
-
|
|
106
|
-
# One row per worker name currently reported, waiting past STALL_THRESHOLD_S
|
|
107
|
-
# or waiting with no stamp to measure, so a reader watching the log is told
|
|
108
|
-
# once rather than on every remaining pass. The prototype for that shape is
|
|
109
|
-
# the WORKER-GONE/WORKER pair above, tracked in the same state a status change
|
|
110
|
-
# is: a name drops out the pass it stops reading "waiting", which lets the
|
|
111
|
-
# same worker report a second stall later in its life rather than being
|
|
112
|
-
# marked forever by its first one.
|
|
113
|
-
declare -A stalled
|
|
114
|
-
|
|
115
|
-
while true; do
|
|
116
|
-
pulls="$(read_pulls)"
|
|
117
|
-
pulls_read=$?
|
|
118
|
-
workers="$(read_workers)"
|
|
119
|
-
workers_read=$?
|
|
120
|
-
|
|
121
|
-
if [ "$pulls_read" -ne 0 ]; then
|
|
122
|
-
echo "watch: the open pull request list failed to load, so none is classified this pass"
|
|
123
|
-
fi
|
|
124
|
-
if [ "$workers_read" -ne 0 ]; then
|
|
125
|
-
echo "watch: the session roster failed to load, so no worker is classified this pass"
|
|
126
|
-
fi
|
|
127
|
-
|
|
128
|
-
names="$(printf '%s\n' "$workers" | awk -F'\t' 'NF {print $1}' | sort -u)"
|
|
129
|
-
|
|
130
|
-
if [ "$pulls_seen" -eq 1 ] && [ "$pulls_read" -eq 0 ]; then
|
|
131
|
-
comm -13 <(printf '%s\n' "$prev_pulls") <(printf '%s\n' "$pulls") | grep . || true
|
|
132
|
-
fi
|
|
133
|
-
|
|
134
|
-
if [ "$workers_seen" -eq 1 ] && [ "$workers_read" -eq 0 ]; then
|
|
135
|
-
comm -13 <(printf '%s\n' "$prev_workers") <(printf '%s\n' "$workers") |
|
|
136
|
-
grep . | sed 's/^/WORKER /' || true
|
|
137
|
-
comm -23 <(printf '%s\n' "$prev_names") <(printf '%s\n' "$names") |
|
|
138
|
-
grep . | sed 's/^/WORKER-GONE /' || true
|
|
139
|
-
fi
|
|
140
|
-
|
|
141
|
-
if [ "$workers_read" -eq 0 ]; then
|
|
142
|
-
while IFS=$'\t' read -r w_name w_branch w_status w_dwell_ms; do
|
|
143
|
-
[ -z "$w_name" ] && continue
|
|
144
|
-
|
|
145
|
-
if [ "$w_status" != "waiting" ]; then
|
|
146
|
-
unset "stalled[$w_name]"
|
|
147
|
-
continue
|
|
148
|
-
fi
|
|
149
|
-
|
|
150
|
-
if [ "$w_dwell_ms" = "-1" ]; then
|
|
151
|
-
if [ -z "${stalled[$w_name]:-}" ]; then
|
|
152
|
-
echo "WORKER-UNMEASURABLE $w_name $w_branch"
|
|
153
|
-
stalled[$w_name]=1
|
|
154
|
-
fi
|
|
155
|
-
continue
|
|
156
|
-
fi
|
|
157
|
-
|
|
158
|
-
w_dwell_s=$((w_dwell_ms / 1000))
|
|
159
|
-
if [ "$w_dwell_s" -ge "$STALL_THRESHOLD_S" ]; then
|
|
160
|
-
if [ -z "${stalled[$w_name]:-}" ]; then
|
|
161
|
-
echo "WORKER-STOPPED $w_name $w_branch ${w_dwell_s}s"
|
|
162
|
-
stalled[$w_name]=1
|
|
163
|
-
fi
|
|
164
|
-
else
|
|
165
|
-
unset "stalled[$w_name]"
|
|
166
|
-
fi
|
|
167
|
-
done <<<"$workers"
|
|
168
|
-
fi
|
|
169
|
-
|
|
170
|
-
if [ "$pulls_read" -eq 0 ]; then
|
|
171
|
-
prev_pulls="$pulls"
|
|
172
|
-
pulls_seen=1
|
|
173
|
-
fi
|
|
174
|
-
if [ "$workers_read" -eq 0 ]; then
|
|
175
|
-
prev_workers="$workers"
|
|
176
|
-
prev_names="$names"
|
|
177
|
-
workers_seen=1
|
|
178
|
-
fi
|
|
179
|
-
|
|
180
|
-
sleep "$INTERVAL"
|
|
181
|
-
done
|