autonomous-sdlc-harness 0.6.2 → 0.6.4
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/commands/init.js +3 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/config/check.js +3 -2
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +1 -1
- package/dist/config/model.js.map +1 -1
- package/dist/doctor/checks.js +30 -12
- package/dist/doctor/checks.js.map +1 -1
- package/dist/remote/githubActions.js +45 -3
- package/dist/remote/githubActions.js.map +1 -1
- package/package.json +1 -1
- package/templates/github/workflows/harness-control.yml +114 -16
- package/templates/github/workflows/harness-resume.yml +6 -5
- package/templates/github/workflows/harness-run.yml +39 -8
- package/templates/scripts/autonomous-watcher.sh +125 -22
- package/templates/scripts/lib/harness-run-lib.sh +140 -13
- package/templates/scripts/push-branch.sh +113 -14
- package/templates/scripts/remote-run.sh +1972 -301
- package/templates/state-dir/user_reviews/README.md +1 -1
|
@@ -357,9 +357,21 @@
|
|
|
357
357
|
# restored bundle's under HARNESS_INPUT_CHAIN above 0, else at this run's own
|
|
358
358
|
# `createdAt` (`remote-run.sh run-created-at "$GITHUB_RUN_ID"`), else at
|
|
359
359
|
# HARNESS_JOB_STARTED_EPOCH — NEVER at the job's own start alone, which
|
|
360
|
-
# would lose a pause sent while the job was queued.
|
|
361
|
-
#
|
|
362
|
-
# advances
|
|
360
|
+
# would lose a pause sent while the job was queued. That starting bound is
|
|
361
|
+
# the job's FLOOR. The verb's exit decides: `0` a pause, `5` none — each
|
|
362
|
+
# advances the bound to the larger of the floor and the epoch taken just
|
|
363
|
+
# before the query less CONTROL_POLL_OVERLAP_SECS, so a run the listing
|
|
364
|
+
# shows late is still read; anything else, `1` included, is a failed poll,
|
|
365
|
+
# which advances nothing and pauses nothing. Every poll logs one line,
|
|
366
|
+
# `job: control poll of '<branch>' since <since> (exit <rc>): <last line>`.
|
|
367
|
+
# * THE RUNNER WAIT: the job logs HARNESS_JOB_STARTED_EPOCH less this run's
|
|
368
|
+
# `createdAt` — read once per job, the same read the control poll's
|
|
369
|
+
# starting bound takes. On a re-run attempt (`GITHUB_RUN_ATTEMPT` above 1)
|
|
370
|
+
# that `createdAt` is the first attempt's, so the wait is not measured and
|
|
371
|
+
# no note is added. A wait of at least RUNNER_WAIT_NOTE_SECS is noted on
|
|
372
|
+
# the `resumed` comment the job posts as it starts, never on a later
|
|
373
|
+
# automatic resume's. The budget already starts at
|
|
374
|
+
# HARNESS_JOB_STARTED_EPOCH, so a wait costs no budget.
|
|
363
375
|
# * THE DECISION, when the run leaves `running`: `paused` for `budget` ->
|
|
364
376
|
# `continue`; for `user` -> `stop`; by the usage gate (`usage`) -> a lost
|
|
365
377
|
# `usage_resume_at` is first given the gate's fallback and reported as
|
|
@@ -396,6 +408,19 @@
|
|
|
396
408
|
# issue and moves its state label when `forge` is `github`; `autonomous-notify.sh`
|
|
397
409
|
# is unchanged, and `completed` is reported by the workflow's `deliver` step
|
|
398
410
|
# after the push.
|
|
411
|
+
# * THE PROGRESS PASS (job_progress_pass): every `running` pass reads the
|
|
412
|
+
# flow-progress ledger the flow pushes in this checkout through
|
|
413
|
+
# lib/harness-run-lib.sh's `hr_ledger_phases`, and calls `remote-run.sh
|
|
414
|
+
# report progress <branch>` when that reading differs from the last one
|
|
415
|
+
# this job reported, and once more when the job ends, so a tick landing
|
|
416
|
+
# after the last poll is still reported. It reads nothing from GitHub to
|
|
417
|
+
# decide and keeps no state across jobs: the render is deterministic and
|
|
418
|
+
# `report progress` edits the comment only when its body differs, so a
|
|
419
|
+
# job's first pass syncs once and a budget continuation posts nothing new
|
|
420
|
+
# unless a phase moved — consistent with `docs/github-run-control.md` ->
|
|
421
|
+
# §5's *The budget is silent*, because what is posted is a phase, not the
|
|
422
|
+
# continuation. What is posted, where, and whether at all is `report
|
|
423
|
+
# progress`'s to decide; the watcher decides only when to call it.
|
|
399
424
|
# * THE INTERACTIVE-TEST PHASE IS SKIPPED, not run: a runner has no browser
|
|
400
425
|
# wiring, application dependencies or QA credentials for it. With
|
|
401
426
|
# `phases.qa` true, the task and user_review launch prompts gain one clause
|
|
@@ -686,8 +711,14 @@
|
|
|
686
711
|
# A stub writing PAUSE_ACK unrequested, then exiting 0 ->
|
|
687
712
|
# one auto-resume, `job: completed stop`; a stub ending
|
|
688
713
|
# `exit 2` with REMOTE_AUTO_RESUME_DELAY_SECS=0 -> launched
|
|
689
|
-
# 1 + REMOTE_AUTO_RESUME_MAX times, `job: failed stop
|
|
690
|
-
#
|
|
714
|
+
# 1 + REMOTE_AUTO_RESUME_MAX times, `job: failed stop`. A
|
|
715
|
+
# stub writing a task ledger at
|
|
716
|
+
# "$d/sdlc-harness/flow_progress/feat_x_progress.md" and
|
|
717
|
+
# sleeping past a few polls, the ledger never ticked ->
|
|
718
|
+
# the watcher log carries one `remote-run.sh: report:` line
|
|
719
|
+
# from the first pass and one from the job's end, none
|
|
720
|
+
# between
|
|
721
|
+
# remote drop `a drop`'s fixture, with "$d"'s own files committed and pushed
|
|
691
722
|
# to origin's default branch, `"execution":{"target":
|
|
692
723
|
# "github-actions"}` in its harness.config.json, and
|
|
693
724
|
# HARNESS_GH_CLI pointed at a recorder (see remote-run.sh's
|
|
@@ -1111,6 +1142,11 @@ USAGE_WARNING_STREAK=0
|
|
|
1111
1142
|
# none of them. How often the control poll asks GitHub for a `harness pause
|
|
1112
1143
|
# <branch>` run — each poll is one `gh run list`.
|
|
1113
1144
|
REMOTE_CONTROL_POLL_SECS="${REMOTE_CONTROL_POLL_SECS:-60}"
|
|
1145
|
+
# Not a tunable. How far each control poll's next lower bound reaches back:
|
|
1146
|
+
# GitHub's run listing is eventually consistent, so the window re-reads the last
|
|
1147
|
+
# five minutes. A marker seen twice is harmless — JOB_USER_PAUSE_DROPPED drops
|
|
1148
|
+
# PAUSE once per job.
|
|
1149
|
+
CONTROL_POLL_OVERLAP_SECS=300
|
|
1114
1150
|
# The longest usage-reset wait a HOSTED job sits through rather than handing
|
|
1115
1151
|
# the run to the resume poller, since a hosted job bills for the minutes it
|
|
1116
1152
|
# waits. A self-hosted job waits for any reset before its deadline.
|
|
@@ -1121,6 +1157,9 @@ REMOTE_AUTO_RESUME_MAX="${REMOTE_AUTO_RESUME_MAX:-2}"
|
|
|
1121
1157
|
# How long job mode waits before each of those resumes, so a transient outage
|
|
1122
1158
|
# has time to clear.
|
|
1123
1159
|
REMOTE_AUTO_RESUME_DELAY_SECS="${REMOTE_AUTO_RESUME_DELAY_SECS:-300}"
|
|
1160
|
+
# The runner wait at or above which the job's `resumed` comment says so (see
|
|
1161
|
+
# THE RUNNER WAIT in the header).
|
|
1162
|
+
RUNNER_WAIT_NOTE_SECS=300
|
|
1124
1163
|
|
|
1125
1164
|
# JOB MODE (see the header). Assigned HERE, after the override channel and every
|
|
1126
1165
|
# tunable default, so neither the file nor an inherited value can turn a
|
|
@@ -1129,9 +1168,18 @@ REMOTE_AUTO_RESUME_DELAY_SECS="${REMOTE_AUTO_RESUME_DELAY_SECS:-300}"
|
|
|
1129
1168
|
JOB_MODE=0
|
|
1130
1169
|
# Job mode's pass state, for the same reason: when the control poll last ran,
|
|
1131
1170
|
# whether each pass has already dropped its one PAUSE, and the job's start
|
|
1132
|
-
# (HARNESS_JOB_STARTED_EPOCH, else when run_job began)
|
|
1171
|
+
# (HARNESS_JOB_STARTED_EPOCH, else when run_job began), and the control poll's
|
|
1172
|
+
# starting bound, below which no later bound falls.
|
|
1133
1173
|
JOB_START_EPOCH=0
|
|
1174
|
+
JOB_CONTROL_FLOOR=0
|
|
1175
|
+
# This run's `createdAt`, read once per job (empty when unread), and the note
|
|
1176
|
+
# job_report adds to a `resumed` report when the runner wait was long.
|
|
1177
|
+
JOB_RUN_CREATED_AT=""
|
|
1178
|
+
JOB_RUNNER_WAIT_NOTE=""
|
|
1134
1179
|
LAST_CONTROL_POLL=0
|
|
1180
|
+
# The ledger reading the progress pass last reported; empty, so a job's first
|
|
1181
|
+
# pass always reports once.
|
|
1182
|
+
JOB_PROGRESS_LAST=""
|
|
1135
1183
|
JOB_USER_PAUSE_DROPPED=0
|
|
1136
1184
|
JOB_BUDGET_PAUSE_DROPPED=0
|
|
1137
1185
|
if [ "${1:-}" = "job" ]; then
|
|
@@ -1185,16 +1233,21 @@ notify() {
|
|
|
1185
1233
|
|
|
1186
1234
|
# job_report <event> <branch> [<log>] — the only route to GitHub. The detail is
|
|
1187
1235
|
# withheld: it names local slash commands, and `report` words its comment from
|
|
1188
|
-
# the registry record itself.
|
|
1236
|
+
# the registry record itself. A `resumed` report carries JOB_RUNNER_WAIT_NOTE
|
|
1237
|
+
# when it is set.
|
|
1189
1238
|
job_report() {
|
|
1239
|
+
local note=()
|
|
1190
1240
|
if [ ! -r "$REMOTE_RUN" ]; then
|
|
1191
1241
|
log "notify: '$REMOTE_RUN' is not readable — '${1:-?}' event for '${2:-?}' not reported on GitHub"
|
|
1192
1242
|
return 0
|
|
1193
1243
|
fi
|
|
1244
|
+
if [ "$1" = "resumed" ] && [ -n "$JOB_RUNNER_WAIT_NOTE" ]; then
|
|
1245
|
+
note=(--note "$JOB_RUNNER_WAIT_NOTE")
|
|
1246
|
+
fi
|
|
1194
1247
|
if [ -n "${3:-}" ]; then
|
|
1195
|
-
bash "$REMOTE_RUN" report "$1" "$2" --repo "$MAIN_REPO" >>"$3" 2>&1 || true
|
|
1248
|
+
bash "$REMOTE_RUN" report "$1" "$2" ${note[@]+"${note[@]}"} --repo "$MAIN_REPO" >>"$3" 2>&1 || true
|
|
1196
1249
|
else
|
|
1197
|
-
bash "$REMOTE_RUN" report "$1" "$2" --repo "$MAIN_REPO" >/dev/null 2>&1 || true
|
|
1250
|
+
bash "$REMOTE_RUN" report "$1" "$2" ${note[@]+"${note[@]}"} --repo "$MAIN_REPO" >/dev/null 2>&1 || true
|
|
1198
1251
|
fi
|
|
1199
1252
|
}
|
|
1200
1253
|
|
|
@@ -1304,7 +1357,10 @@ job_report() {
|
|
|
1304
1357
|
# API-overload self-pause. `user` and `budget` are written
|
|
1305
1358
|
# before the PAUSE is dropped, while still `running`;
|
|
1306
1359
|
# classify_run_exit settles the reason as the run pauses,
|
|
1307
|
-
# `user` first, then `usage`, then `budget`.
|
|
1360
|
+
# `user` first, then `usage`, then `budget`. A `user` still
|
|
1361
|
+
# set when the run exits `parked` or `park_loop` is read by
|
|
1362
|
+
# `remote-run.sh report` for that park's comment, and
|
|
1363
|
+
# run_job clears it at the job's end. Cleared when
|
|
1308
1364
|
# job mode relaunches the run — plus `killed`, a registry-only
|
|
1309
1365
|
# value `remote-run.sh sync` derives when a finished run's
|
|
1310
1366
|
# bundle still says `running`, or a finished run left no
|
|
@@ -1330,11 +1386,11 @@ job_report() {
|
|
|
1330
1386
|
# 0, else `0` — so ANY USER ACTION (a drop, an answer, a
|
|
1331
1387
|
# resume: each a chain-0 dispatch) resets it and restores
|
|
1332
1388
|
# the full allowance
|
|
1333
|
-
# control_polled_at job mode only: the
|
|
1334
|
-
#
|
|
1335
|
-
#
|
|
1336
|
-
#
|
|
1337
|
-
#
|
|
1389
|
+
# control_polled_at job mode only: the lower bound of the next control poll,
|
|
1390
|
+
# this job's or the next chained one's — set at start (see
|
|
1391
|
+
# JOB MODE), and after each successful poll the larger of
|
|
1392
|
+
# that starting bound and the epoch before the query less
|
|
1393
|
+
# CONTROL_POLL_OVERLAP_SECS
|
|
1338
1394
|
# pause_note_stale job mode only: `1` when a `pause` job's restored
|
|
1339
1395
|
# `status.json` was not `paused` or named another engine,
|
|
1340
1396
|
# so spawn_engine's pause-resume prompt says there is no
|
|
@@ -4022,6 +4078,30 @@ job_int() {
|
|
|
4022
4078
|
printf '%s\n' "$((10#$1))"
|
|
4023
4079
|
}
|
|
4024
4080
|
|
|
4081
|
+
# job_runner_wait — the job's one `run-created-at` read, into JOB_RUN_CREATED_AT,
|
|
4082
|
+
# then THE RUNNER WAIT's log line and JOB_RUNNER_WAIT_NOTE (the header).
|
|
4083
|
+
job_runner_wait() {
|
|
4084
|
+
local wait mins
|
|
4085
|
+
if [ -n "${GITHUB_RUN_ID:-}" ]; then
|
|
4086
|
+
JOB_RUN_CREATED_AT="$(job_int "$(bash "$REMOTE_RUN" run-created-at "$GITHUB_RUN_ID" --repo "$MAIN_REPO" 2>>"$WATCHER_LOG")")" || JOB_RUN_CREATED_AT=""
|
|
4087
|
+
fi
|
|
4088
|
+
if [ -z "$JOB_RUN_CREATED_AT" ]; then
|
|
4089
|
+
log "job: this run's createdAt could not be read — the runner wait is unknown"
|
|
4090
|
+
return 0
|
|
4091
|
+
fi
|
|
4092
|
+
if [ "${GITHUB_RUN_ATTEMPT:-1}" != 1 ]; then
|
|
4093
|
+
log "job: run attempt ${GITHUB_RUN_ATTEMPT} — this run's createdAt is its first attempt's, so the runner wait is not measured"
|
|
4094
|
+
return 0
|
|
4095
|
+
fi
|
|
4096
|
+
wait=$((JOB_START_EPOCH - JOB_RUN_CREATED_AT))
|
|
4097
|
+
[ "$wait" -ge 0 ] || wait=0
|
|
4098
|
+
log "job: waited ${wait}s for a runner (run created $JOB_RUN_CREATED_AT, job started $JOB_START_EPOCH)"
|
|
4099
|
+
if [ "$wait" -ge "$RUNNER_WAIT_NOTE_SECS" ]; then
|
|
4100
|
+
mins=$(((wait + 59) / 60))
|
|
4101
|
+
JOB_RUNNER_WAIT_NOTE="GitHub took $mins minutes to start this job, so nothing moved until then."
|
|
4102
|
+
fi
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4025
4105
|
# job_start_control_bound <branch> <remote_status> — the control poll's first
|
|
4026
4106
|
# lower bound, in the order the header's JOB MODE block states.
|
|
4027
4107
|
job_start_control_bound() {
|
|
@@ -4029,35 +4109,40 @@ job_start_control_bound() {
|
|
|
4029
4109
|
if [ "$HARNESS_INPUT_CHAIN" -gt 0 ] && [ -f "$remote_status" ]; then
|
|
4030
4110
|
bound="$(job_int "$(hr_remote_status_get "$remote_status" control_polled_at)")" || bound=""
|
|
4031
4111
|
fi
|
|
4032
|
-
|
|
4033
|
-
bound="$(job_int "$(bash "$REMOTE_RUN" run-created-at "$GITHUB_RUN_ID" --repo "$MAIN_REPO" 2>>"$WATCHER_LOG")")" || bound=""
|
|
4034
|
-
fi
|
|
4112
|
+
[ -n "$bound" ] || bound="$JOB_RUN_CREATED_AT"
|
|
4035
4113
|
if [ -z "$bound" ]; then
|
|
4036
4114
|
bound="$JOB_START_EPOCH"
|
|
4037
4115
|
log "job: could not read this run's createdAt — the control poll starts from the job's start ($bound)"
|
|
4038
4116
|
fi
|
|
4117
|
+
JOB_CONTROL_FLOOR="$bound"
|
|
4039
4118
|
registry_set "$branch" control_polled_at "$bound"
|
|
4040
4119
|
}
|
|
4041
4120
|
|
|
4042
4121
|
# job_control_poll <branch> <state_abs> <remote_status> — the `user` pass.
|
|
4122
|
+
# Exit map of `pause-requested`: 0 a pause, 5 none, anything else a failed poll
|
|
4123
|
+
# that neither moves the bound nor pauses.
|
|
4043
4124
|
job_control_poll() {
|
|
4044
|
-
local branch="$1" state_abs="$2" remote_status="$3" now since before rc
|
|
4125
|
+
local branch="$1" state_abs="$2" remote_status="$3" now since before rc out next
|
|
4045
4126
|
[ "$JOB_USER_PAUSE_DROPPED" = "0" ] || return 0
|
|
4046
4127
|
now="$(date +%s)"
|
|
4047
4128
|
[ $((now - LAST_CONTROL_POLL)) -ge "$REMOTE_CONTROL_POLL_SECS" ] || return 0
|
|
4048
4129
|
LAST_CONTROL_POLL="$now"
|
|
4049
4130
|
since="$(job_int "$(registry_get "$branch" control_polled_at)")" || since="$JOB_START_EPOCH"
|
|
4050
4131
|
before="$(date +%s)"
|
|
4051
|
-
bash "$REMOTE_RUN" pause-requested "$branch" "$since" --repo "$MAIN_REPO"
|
|
4132
|
+
out="$(bash "$REMOTE_RUN" pause-requested "$branch" "$since" --repo "$MAIN_REPO" 2>&1)"
|
|
4052
4133
|
rc=$?
|
|
4134
|
+
[ -z "$out" ] || printf '%s\n' "$out" >>"$WATCHER_LOG"
|
|
4135
|
+
log "job: control poll of '$branch' since $since (exit $rc): $(printf '%s\n' "$out" | awk 'NF { l = $0 } END { print l }')"
|
|
4053
4136
|
case "$rc" in
|
|
4054
|
-
0 |
|
|
4137
|
+
0 | 5) ;;
|
|
4055
4138
|
*)
|
|
4056
4139
|
log "job: the control poll for '$branch' failed (exit $rc) — not pausing; control_polled_at stays $since"
|
|
4057
4140
|
return 0
|
|
4058
4141
|
;;
|
|
4059
4142
|
esac
|
|
4060
|
-
|
|
4143
|
+
next=$((before - CONTROL_POLL_OVERLAP_SECS))
|
|
4144
|
+
[ "$next" -ge "$JOB_CONTROL_FLOOR" ] || next="$JOB_CONTROL_FLOOR"
|
|
4145
|
+
registry_set "$branch" control_polled_at "$next"
|
|
4061
4146
|
if [ "$rc" = "0" ]; then
|
|
4062
4147
|
JOB_USER_PAUSE_DROPPED=1
|
|
4063
4148
|
registry_set "$branch" pause_reason user
|
|
@@ -4080,6 +4165,17 @@ job_budget_pass() {
|
|
|
4080
4165
|
log "job: ${after}s of the hosted time budget have passed — dropped PAUSE (reason budget)"
|
|
4081
4166
|
}
|
|
4082
4167
|
|
|
4168
|
+
# job_progress_pass <branch> <state_abs> [final] — THE PROGRESS PASS (the
|
|
4169
|
+
# header's JOB MODE block). `final` reports even an unchanged reading.
|
|
4170
|
+
job_progress_pass() {
|
|
4171
|
+
local branch="$1" state_abs="$2" line
|
|
4172
|
+
[ "$JOB_MODE" = "1" ] || return 0
|
|
4173
|
+
line="$(hr_ledger_phases "$state_abs/flow_progress/${branch}_progress.md")" || return 0
|
|
4174
|
+
[ "$line" != "$JOB_PROGRESS_LAST" ] || [ "${3:-}" = "final" ] || return 0
|
|
4175
|
+
JOB_PROGRESS_LAST="$line"
|
|
4176
|
+
bash "$REMOTE_RUN" report progress "$branch" --repo "$MAIN_REPO" >>"$WATCHER_LOG" 2>&1 || true
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4083
4179
|
# job_usage_wait_ok <branch> — 0 when a usage pause is waited out in the job;
|
|
4084
4180
|
# leaves usage_resume_at_var's USAGE_RESUME_AT and USAGE_RESUME_REPAIRED set.
|
|
4085
4181
|
# With no usable usage_resume_at: `paused_by=usage` still set means the value was
|
|
@@ -4145,6 +4241,7 @@ run_job() {
|
|
|
4145
4241
|
local prev_status="" prev_engine="" aside_rc
|
|
4146
4242
|
|
|
4147
4243
|
JOB_START_EPOCH="$(job_int "${HARNESS_JOB_STARTED_EPOCH:-}")" || JOB_START_EPOCH="$(date +%s)"
|
|
4244
|
+
job_runner_wait
|
|
4148
4245
|
state_rel="$(run_state_dir "$worktree")" || fatal "job: the state directory in '$worktree' is unresolvable"
|
|
4149
4246
|
state_abs="$worktree/$state_rel"
|
|
4150
4247
|
clar_dir="$state_abs/clarifications/$branch"
|
|
@@ -4248,6 +4345,10 @@ run_job() {
|
|
|
4248
4345
|
;;
|
|
4249
4346
|
esac || registry_set "$branch" status failed
|
|
4250
4347
|
|
|
4348
|
+
# The runner-wait note belongs to the job's own start: a later automatic
|
|
4349
|
+
# resume's `resumed` comment never carries it (THE RUNNER WAIT in the header).
|
|
4350
|
+
JOB_RUNNER_WAIT_NOTE=""
|
|
4351
|
+
|
|
4251
4352
|
# The supervision loop: the header's JOB MODE block states each decision.
|
|
4252
4353
|
local status reason ra when decision="stop" detail="" usage_waiting=0 restarts
|
|
4253
4354
|
local wait_ok=1 usage_wait_start=0 usage_wait_ra=0
|
|
@@ -4263,6 +4364,7 @@ run_job() {
|
|
|
4263
4364
|
[ "$(registry_get "$branch" status)" = "running" ] || continue
|
|
4264
4365
|
job_control_poll "$branch" "$state_abs" "$remote_status"
|
|
4265
4366
|
job_budget_pass "$branch" "$state_abs"
|
|
4367
|
+
job_progress_pass "$branch" "$state_abs"
|
|
4266
4368
|
;;
|
|
4267
4369
|
paused)
|
|
4268
4370
|
reason="$(registry_get "$branch" pause_reason)"
|
|
@@ -4354,6 +4456,7 @@ run_job() {
|
|
|
4354
4456
|
[ "$final" = "paused" ] || registry_set "$branch" pause_reason ""
|
|
4355
4457
|
[ -n "$detail" ] || detail="the run ended $final in this job"
|
|
4356
4458
|
[ "$final" != "failed" ] || job_report failed "$branch" "$log_path"
|
|
4459
|
+
job_progress_pass "$branch" "$state_abs" final
|
|
4357
4460
|
job_write_status "$branch" "$remote_status" "$decision" "$detail"
|
|
4358
4461
|
echo "job: $final $decision"
|
|
4359
4462
|
exit 0
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
# routes an inbox filename to its engine and branch (`hr_inbox_route_var`),
|
|
6
6
|
# derives a branch name from a title (`hr_derive_branch`), judges whether a
|
|
7
7
|
# path lies strictly inside the state directory's scratch directory
|
|
8
|
-
# (`hr_scratch_path_var`),
|
|
8
|
+
# (`hr_scratch_path_var`), answers whether the progress comment is on
|
|
9
|
+
# (`hr_progress_comments`) and which main phases a flow-progress ledger records
|
|
10
|
+
# as through (`hr_ledger_phases`), places a dropped
|
|
9
11
|
# artifact in a working copy and commits and pushes it, and derives the
|
|
10
12
|
# anchors (main checkout, work root, worktree directory, repo slug,
|
|
11
13
|
# state-dir paths) the scripts would otherwise each re-derive slightly
|
|
@@ -87,7 +89,10 @@
|
|
|
87
89
|
# file is ever removed.
|
|
88
90
|
# 4. THE ARTIFACT PLACEMENT writes one artifact into a working copy. Fence:
|
|
89
91
|
# the caller-named `<worktree>/<rel>`, its parent directories and that
|
|
90
|
-
# path's index entry, plus whatever the two caller-named wrappers do
|
|
92
|
+
# path's index entry, plus whatever the two caller-named wrappers do, plus
|
|
93
|
+
# one write of its own: `hr_push_landed`'s fetch, which force-writes
|
|
94
|
+
# `refs/remotes/origin/<branch>` in `<worktree>`, made only after a failed
|
|
95
|
+
# landing, to name the commit the remote moved to.
|
|
91
96
|
# Written only by `hr_place_artifact`, `hr_commit_placed` and
|
|
92
97
|
# `hr_push_landed`.
|
|
93
98
|
#
|
|
@@ -96,6 +101,11 @@
|
|
|
96
101
|
# HR_REMOTE_WORKFLOW_RUN_FILE mirrors WORKFLOW_RUN_FILE
|
|
97
102
|
# HR_REMOTE_STATE_ARTIFACT mirrors STATE_ARTIFACT_NAME
|
|
98
103
|
#
|
|
104
|
+
# MIRROR OF `plugin/instructions/autonomous_pause_and_ledger.md` →
|
|
105
|
+
# `### 1.3 Templates`, which owns the flow-progress ledger's two header forms
|
|
106
|
+
# and its entry ids; `hr_ledger_phases` codes both, so renaming an id or a
|
|
107
|
+
# header form there is an edit here.
|
|
108
|
+
#
|
|
99
109
|
# A caller that calls no `hr_lane_*`, `hr_registry_init`, `hr_registry_set`,
|
|
100
110
|
# `hr_remote_record_init`, `hr_registry_lock`, `hr_registry_unlock`, `hr_remote_status_write`,
|
|
101
111
|
# `hr_remote_bundle_write`, `hr_remote_bundle_restore`, `hr_remote_move_aside`, `hr_place_artifact`,
|
|
@@ -600,10 +610,11 @@ hr_config_load() {
|
|
|
600
610
|
# which is what lets an explicitly EMPTY list read as a configured set rather
|
|
601
611
|
# than as an absent one.
|
|
602
612
|
#
|
|
603
|
-
# A `phases
|
|
604
|
-
# `invalid`, so the string `"true"` — which
|
|
605
|
-
# indistinguishable from `true` — reaches
|
|
606
|
-
# `hr_docs_retrieval_applies`
|
|
613
|
+
# A `phases.*`, `docs.retrieval` or `execution.progressComments` value that is
|
|
614
|
+
# not a boolean is emitted as `invalid`, so the string `"true"` — which
|
|
615
|
+
# `tostring` would otherwise make indistinguishable from `true` — reaches
|
|
616
|
+
# `hr_phase_enabled`, `hr_docs_retrieval_applies` or `hr_progress_comments` as
|
|
617
|
+
# a value it refuses (2).
|
|
607
618
|
out=$(jq -n -r '
|
|
608
619
|
def s($k; $v):
|
|
609
620
|
if $v == null then empty
|
|
@@ -638,6 +649,8 @@ hr_config_load() {
|
|
|
638
649
|
s("phases.qa"; try (.phases.qa | if type == "boolean" or . == null then . else "invalid" end) catch null),
|
|
639
650
|
s("phases.docs"; try (.phases.docs | if type == "boolean" or . == null then . else "invalid" end) catch null),
|
|
640
651
|
s("execution.target"; try .execution.target catch null),
|
|
652
|
+
s("execution.progressComments";
|
|
653
|
+
try (.execution.progressComments | if type == "boolean" or . == null then . else "invalid" end) catch null),
|
|
641
654
|
s("docs.retrievalBackend"; try .docs.retrievalBackend catch null),
|
|
642
655
|
s("docs.retrieval"; try (.docs.retrieval | if type == "boolean" or . == null then . else "invalid" end) catch null),
|
|
643
656
|
s("forge"; try .forge catch null),
|
|
@@ -927,6 +940,107 @@ hr_forge() {
|
|
|
927
940
|
return 2
|
|
928
941
|
}
|
|
929
942
|
|
|
943
|
+
# `execution.progressComments` — whether a remote run keeps its progress
|
|
944
|
+
# comment on the pull request. THE ONE READER OF THE KEY IN THIS FAMILY; a
|
|
945
|
+
# script that needs it calls this. PRINTS NOTHING: the answer is the status, as
|
|
946
|
+
# `hr_phase_enabled`'s is, except that an absent key is the schema default
|
|
947
|
+
# `true`. 0 = `true` or absent; 1 = `false`; 2 = the configuration is
|
|
948
|
+
# unresolvable or the value is not a boolean — refused rather than guessed about.
|
|
949
|
+
hr_progress_comments() {
|
|
950
|
+
local root="${1-}"
|
|
951
|
+
hr_config_load "$root" || return 2
|
|
952
|
+
hr_cfg_scalar_var "execution.progressComments" || return 0
|
|
953
|
+
case "$HR_CFG_VALUE" in
|
|
954
|
+
true) return 0 ;;
|
|
955
|
+
false) return 1 ;;
|
|
956
|
+
esac
|
|
957
|
+
return 2
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
# ---------------------------------------------------------------------------
|
|
961
|
+
# The flow-progress ledger — which of a run's four main phases it records as
|
|
962
|
+
# through. A mirror of the plugin's ledger templates; see the header.
|
|
963
|
+
# ---------------------------------------------------------------------------
|
|
964
|
+
|
|
965
|
+
# 0 when every id after <settled> appears in <settled>, a space-delimited list
|
|
966
|
+
# with a leading and trailing space; 1 otherwise.
|
|
967
|
+
hr_ledger_all_settled() {
|
|
968
|
+
local settled="${1-}" id
|
|
969
|
+
shift
|
|
970
|
+
for id in "$@"; do
|
|
971
|
+
case "$settled" in
|
|
972
|
+
*" $id "*) ;;
|
|
973
|
+
*) return 1 ;;
|
|
974
|
+
esac
|
|
975
|
+
done
|
|
976
|
+
return 0
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
# Print `<engine> <round> <phase1> <phase2> <phase3> <phase4>` for the ledger at
|
|
980
|
+
# <ledger_file> and return 0 — `<engine>` is `task` or `user_review`, `<round>`
|
|
981
|
+
# the header's round or `-` for the task engine, each phase `done` or `pending`.
|
|
982
|
+
# Return 1, printing nothing, when the file is missing or unreadable or its first
|
|
983
|
+
# line is not a task-engine or user-review-engine header: any other ledger has
|
|
984
|
+
# no phases to report.
|
|
985
|
+
#
|
|
986
|
+
# A phase is `done` when every id of its set is `[x]` or `[-]` (skipped before
|
|
987
|
+
# the run began). An id absent from the file is NOT settled, so a malformed
|
|
988
|
+
# ledger reads as `pending`, never as `done`. Phase 3 groups every end-of-branch
|
|
989
|
+
# review with its fixes, QA and the run gates.
|
|
990
|
+
hr_ledger_phases() {
|
|
991
|
+
local file="${1-}" line first=1 engine="" round="-" settled=" " rest id
|
|
992
|
+
local p1 p2 p3 p4 s1 s2 s3 s4
|
|
993
|
+
[ -n "$file" ] && [ -f "$file" ] && [ -r "$file" ] || return 1
|
|
994
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
995
|
+
if [ "$first" -eq 1 ]; then
|
|
996
|
+
first=0
|
|
997
|
+
case "$line" in
|
|
998
|
+
*"(engine: task)"*)
|
|
999
|
+
engine="task"
|
|
1000
|
+
;;
|
|
1001
|
+
*"(engine: user_review, round "*")"*)
|
|
1002
|
+
engine="user_review"
|
|
1003
|
+
round=${line#*"(engine: user_review, round "}
|
|
1004
|
+
round=${round%%")"*}
|
|
1005
|
+
case "$round" in
|
|
1006
|
+
''|*[!0-9]*) return 1 ;;
|
|
1007
|
+
esac
|
|
1008
|
+
;;
|
|
1009
|
+
*) return 1 ;;
|
|
1010
|
+
esac
|
|
1011
|
+
continue
|
|
1012
|
+
fi
|
|
1013
|
+
case "$line" in
|
|
1014
|
+
"- [x] "*|"- [-] "*) ;;
|
|
1015
|
+
*) continue ;;
|
|
1016
|
+
esac
|
|
1017
|
+
rest=${line#"- ["?"] "}
|
|
1018
|
+
id=${rest%% *}
|
|
1019
|
+
id=${id%.}
|
|
1020
|
+
if [ -n "$id" ]; then settled="$settled$id "; fi
|
|
1021
|
+
done < "$file"
|
|
1022
|
+
[ -n "$engine" ] || return 1
|
|
1023
|
+
|
|
1024
|
+
if [ "$engine" = "task" ]; then
|
|
1025
|
+
p1="P1 P2 P3"
|
|
1026
|
+
p2="A"
|
|
1027
|
+
p3="A1.5g A1.5f A2g A2f Bg Bm C C2g C2m C2f E G"
|
|
1028
|
+
p4="D"
|
|
1029
|
+
else
|
|
1030
|
+
p1="R1 R2"
|
|
1031
|
+
p2="R3"
|
|
1032
|
+
p3="R4 RG"
|
|
1033
|
+
p4="R5"
|
|
1034
|
+
fi
|
|
1035
|
+
# Unquoted on purpose: each set is a fixed list of space-free ids.
|
|
1036
|
+
if hr_ledger_all_settled "$settled" $p1; then s1="done"; else s1="pending"; fi
|
|
1037
|
+
if hr_ledger_all_settled "$settled" $p2; then s2="done"; else s2="pending"; fi
|
|
1038
|
+
if hr_ledger_all_settled "$settled" $p3; then s3="done"; else s3="pending"; fi
|
|
1039
|
+
if hr_ledger_all_settled "$settled" $p4; then s4="done"; else s4="pending"; fi
|
|
1040
|
+
printf '%s %s %s %s %s %s\n' "$engine" "$round" "$s1" "$s2" "$s3" "$s4"
|
|
1041
|
+
return 0
|
|
1042
|
+
}
|
|
1043
|
+
|
|
930
1044
|
# Whether the docs phase's retrieval step runs: `phases.docs` and
|
|
931
1045
|
# `docs.retrieval` both `true`. The shell mirror of `cli/src/config/model.ts` →
|
|
932
1046
|
# `retrievalApplies`: change the predicate there and here together. PRINTS
|
|
@@ -1865,17 +1979,31 @@ hr_commit_placed() {
|
|
|
1865
1979
|
return 0
|
|
1866
1980
|
}
|
|
1867
1981
|
|
|
1868
|
-
# hr_push_landed <push_wrapper> <worktree> <branch> — run <push_wrapper>, then
|
|
1869
|
-
#
|
|
1870
|
-
#
|
|
1871
|
-
#
|
|
1982
|
+
# hr_push_landed <push_wrapper> <worktree> <branch> — run <push_wrapper>, then
|
|
1983
|
+
# answer from refs alone, because `push-branch.sh` exits 0 on every path:
|
|
1984
|
+
# 0 landed: `HEAD` and `refs/remotes/origin/<branch>` resolve and are equal,
|
|
1985
|
+
# before or after that fetch;
|
|
1986
|
+
# 2 the remote moved: after the failed landing, a fetch of
|
|
1987
|
+
# `refs/remotes/origin/<branch>` succeeds and it names a commit that is not
|
|
1988
|
+
# an ancestor of `HEAD`. `HR_PUSH_REMOTE_TIP` is set to its short id;
|
|
1989
|
+
# 1 anything else — the push was refused, the fetch failed, or a ref did not
|
|
1990
|
+
# resolve.
|
|
1991
|
+
# It never retries and never rebases; the retry is `push-branch.sh`'s.
|
|
1872
1992
|
hr_push_landed() {
|
|
1873
1993
|
local wrapper="${1-}" worktree="${2-}" branch="${3-}" head upstream
|
|
1994
|
+
HR_PUSH_REMOTE_TIP=""
|
|
1874
1995
|
[ -n "$wrapper" ] && [ -n "$worktree" ] && [ -n "$branch" ] || return 1
|
|
1875
1996
|
"$wrapper" "$worktree"
|
|
1876
1997
|
head=$(git -C "$worktree" rev-parse --verify --quiet HEAD) || return 1
|
|
1998
|
+
[ -n "$head" ] || return 1
|
|
1999
|
+
upstream=$(git -C "$worktree" rev-parse --verify --quiet "refs/remotes/origin/$branch") \
|
|
2000
|
+
&& [ "$head" = "$upstream" ] && return 0
|
|
2001
|
+
git -C "$worktree" fetch --quiet origin "+refs/heads/$branch:refs/remotes/origin/$branch" || return 1
|
|
1877
2002
|
upstream=$(git -C "$worktree" rev-parse --verify --quiet "refs/remotes/origin/$branch") || return 1
|
|
1878
|
-
[
|
|
2003
|
+
[ "$head" != "$upstream" ] || return 0
|
|
2004
|
+
git -C "$worktree" merge-base --is-ancestor "$upstream" "$head" && return 1
|
|
2005
|
+
HR_PUSH_REMOTE_TIP=$(git -C "$worktree" rev-parse --short "$upstream") || return 1
|
|
2006
|
+
return 2
|
|
1879
2007
|
}
|
|
1880
2008
|
|
|
1881
2009
|
# ---------------------------------------------------------------------------
|
|
@@ -1946,8 +2074,7 @@ hr_push_landed() {
|
|
|
1946
2074
|
# the counters that must survive a job boundary
|
|
1947
2075
|
# chain the writing job's OWN input `HARNESS_INPUT_CHAIN`, never
|
|
1948
2076
|
# a value carried from an earlier bundle
|
|
1949
|
-
# control_polled_at the
|
|
1950
|
-
# `harness pause <branch>` run, or empty
|
|
2077
|
+
# control_polled_at the lower bound of the next control poll, or empty
|
|
1951
2078
|
# decision `continue` | `wait-poller` | `stop`
|
|
1952
2079
|
# detail one human-readable line
|
|
1953
2080
|
# run_id, run_url, written_at
|