autonomous-sdlc-harness 0.4.1 → 0.5.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/commands/init.js +101 -13
- package/dist/commands/init.js.map +1 -1
- package/dist/config/check.js +1 -1
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +17 -1
- package/dist/config/model.js.map +1 -1
- package/dist/core/writer.js +9 -5
- package/dist/core/writer.js.map +1 -1
- package/dist/core/yamlScalar.js +14 -0
- package/dist/core/yamlScalar.js.map +1 -0
- package/dist/doctor/checks.js +169 -15
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/githubWorkflows.js +115 -20
- package/dist/generators/githubWorkflows.js.map +1 -1
- package/dist/generators/repoRoot.js +17 -8
- package/dist/generators/repoRoot.js.map +1 -1
- package/dist/remote/githubActions.js +51 -7
- package/dist/remote/githubActions.js.map +1 -1
- package/package.json +1 -1
- package/templates/README.md +1 -1
- package/templates/github/workflows/harness-resume.yml +23 -5
- package/templates/github/workflows/harness-run.yml +68 -30
- package/templates/github/workflows/harness-trigger.yml +143 -0
- package/templates/repo/gitignore +5 -0
- package/templates/scripts/autonomous-watcher.sh +95 -248
- package/templates/scripts/create-worktree.sh +52 -10
- package/templates/scripts/lib/harness-run-lib.sh +553 -26
- package/templates/scripts/remote-run.sh +1198 -114
|
@@ -210,17 +210,10 @@
|
|
|
210
210
|
# OWN. Skipped for such a record: the vanished-process reconcile, the cap
|
|
211
211
|
# count, the stall watchdog, both halves of the usage gate, and the lane's
|
|
212
212
|
# idle release.
|
|
213
|
-
# *
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
# after a PARK_LOOP_CLEAR); a RESUME -> `dispatch --resume pause`; a PAUSE on
|
|
218
|
-
# a `running` record -> `remote-run.sh pause`. Each is chain 0, takes no cap
|
|
219
|
-
# slot and consults no lane; only a sent dispatch changes the record or
|
|
220
|
-
# removes a file, so a refusal or a `gh` failure is one log line and the
|
|
221
|
-
# next pass retries. The kill switch defers the two resumes; the pause relay
|
|
222
|
-
# runs ahead of it, the one action that still runs under the brake, because
|
|
223
|
-
# it starts nothing.
|
|
213
|
+
# * NOTHING IS RELAYED. The user's commands send pause, resume and answer to
|
|
214
|
+
# GitHub themselves through `remote-run.sh`, so no file in a mirror is a
|
|
215
|
+
# request this script acts on: the parked, paused and park-loop passes skip
|
|
216
|
+
# a remote record outright, sending nothing and changing nothing.
|
|
224
217
|
#
|
|
225
218
|
# A remote run's local working copy is a MIRROR that `remote-run.sh sync`
|
|
226
219
|
# fills; it is stopped through `remote-run.sh stop`.
|
|
@@ -704,17 +697,12 @@
|
|
|
704
697
|
# the same tick still launches under MAX_PARALLEL_RUNS=1, its
|
|
705
698
|
# back-dated mirror logs draw no stall line, and a `rejected`
|
|
706
699
|
# event in its stream drops no PAUSE
|
|
707
|
-
# remote
|
|
708
|
-
# in the mirror's clarifications/feat_x
|
|
709
|
-
#
|
|
710
|
-
#
|
|
711
|
-
#
|
|
712
|
-
#
|
|
713
|
-
# dispatch and PAUSE / RESUME / PAUSE_ACK gone; `running` with
|
|
714
|
-
# the mirror's PAUSE -> ONE `-f action=pause` dispatch, PAUSE
|
|
715
|
-
# gone and pause_relayed_at set. With AUTONOMOUS_STOP present
|
|
716
|
-
# only the pause is sent; with a recorder exiting non-zero
|
|
717
|
-
# every file and status stays
|
|
700
|
+
# remote passes from that record: `parked` with question_1.md and answer_1.md
|
|
701
|
+
# in the mirror's clarifications/feat_x/; `park_loop` plus
|
|
702
|
+
# PARK_LOOP_CLEAR; `paused` with the mirror's RESUME; `running`
|
|
703
|
+
# with the mirror's PAUSE -> each tick sends no `gh` call, does
|
|
704
|
+
# NOT launch the stub, and leaves every mirror file and the
|
|
705
|
+
# record's status as they were
|
|
718
706
|
# unresolvable printf 'x' > "$d/harness.config.json"
|
|
719
707
|
# -> one line on stderr, exit 1, nothing under "$d/sdlc-harness"
|
|
720
708
|
|
|
@@ -1272,7 +1260,9 @@ notify() {
|
|
|
1272
1260
|
# bundle still says `running`, or a finished run left no
|
|
1273
1261
|
# bundle, and `expired`, a registry-only value it derives
|
|
1274
1262
|
# when a finished run's bundle has expired (the job can no
|
|
1275
|
-
# longer take an answer, and the carried counts
|
|
1263
|
+
# longer take an answer, and the carried counts and any
|
|
1264
|
+
# planning drafts not yet committed that it carried are
|
|
1265
|
+
# lost);
|
|
1276
1266
|
# `status.json` never carries either. Both map to
|
|
1277
1267
|
# `paused` rather than `failed` because a `failed` record
|
|
1278
1268
|
# has no resume path, while the ledger on the branch is
|
|
@@ -1295,18 +1285,14 @@ notify() {
|
|
|
1295
1285
|
# bound of the next control poll, this job's or the next
|
|
1296
1286
|
# chained one's. Set at start (see JOB MODE) and advanced
|
|
1297
1287
|
# by every successful poll
|
|
1298
|
-
# execution `github-actions` on a record
|
|
1288
|
+
# execution `github-actions` on a remote record — one
|
|
1289
|
+
# launch_remote_run wrote through
|
|
1290
|
+
# lib/harness-run-lib.sh's `hr_remote_record_init` — and
|
|
1299
1291
|
# absent on a local one. Fixed for the run's life: a later
|
|
1300
1292
|
# pass reads this field, never `execution.target`
|
|
1301
1293
|
# remote_dispatched_at
|
|
1302
1294
|
# the epoch second launch_remote_run's `remote-run.sh
|
|
1303
1295
|
# dispatch` returned 0; empty after a failed dispatch
|
|
1304
|
-
# park_loop_clear_pending
|
|
1305
|
-
# `1` on a remote record clear_park_loops returned to
|
|
1306
|
-
# `parked`, so the next answer relay sends
|
|
1307
|
-
# `--park-loop-clear`; cleared once that relay is sent
|
|
1308
|
-
# pause_relayed_at the epoch second the pause relay sent a remote record's
|
|
1309
|
-
# `remote-run.sh pause`
|
|
1310
1296
|
# -----------------------------------------------------------------------------
|
|
1311
1297
|
# The bodies are lib/harness-run-lib.sh's THE RUN REGISTRY, shared with every
|
|
1312
1298
|
# script that reads or writes this file; these wrappers bind them to $REGISTRY.
|
|
@@ -1559,7 +1545,7 @@ print_status() {
|
|
|
1559
1545
|
}
|
|
1560
1546
|
|
|
1561
1547
|
# The global kill switch — checked before every launch and at the top of every
|
|
1562
|
-
# pass
|
|
1548
|
+
# pass.
|
|
1563
1549
|
# NEVER removed here; see the header.
|
|
1564
1550
|
kill_switch_active() {
|
|
1565
1551
|
[ -f "$GLOBAL_STOP" ]
|
|
@@ -2067,26 +2053,15 @@ launch_run() {
|
|
|
2067
2053
|
# launch_remote_run <branch> <worktree> <log_path> <engine_kind>
|
|
2068
2054
|
#
|
|
2069
2055
|
# launch_run's bookkeeping for a remote drop (REMOTE DISPATCH in the header):
|
|
2070
|
-
# the same fields written and cleared, plus `execution` and an empty pid
|
|
2056
|
+
# the same fields written and cleared, plus `execution` and an empty pid — the
|
|
2057
|
+
# one list is lib/harness-run-lib.sh's `hr_remote_record_init` — then
|
|
2071
2058
|
# `remote-run.sh dispatch` in place of the window and the spawn. `running` is
|
|
2072
2059
|
# written only once the dispatch returned 0, so a record never claims a run
|
|
2073
2060
|
# GitHub was not asked for.
|
|
2074
2061
|
launch_remote_run() {
|
|
2075
2062
|
local branch="$1" worktree="$2" log_path="$3" engine_kind="$4" out rc first
|
|
2076
2063
|
|
|
2077
|
-
|
|
2078
|
-
registry_set "$branch" log_path "$log_path"
|
|
2079
|
-
registry_set "$branch" engine "$engine_kind"
|
|
2080
|
-
registry_set "$branch" execution github-actions
|
|
2081
|
-
registry_set "$branch" started_at "$(date '+%Y-%m-%dT%H:%M:%S')"
|
|
2082
|
-
registry_set "$branch" pid ""
|
|
2083
|
-
registry_set "$branch" remote_dispatched_at ""
|
|
2084
|
-
registry_set "$branch" stall_restarts 0
|
|
2085
|
-
registry_set "$branch" stall_warned ""
|
|
2086
|
-
registry_set "$branch" stall_killing ""
|
|
2087
|
-
registry_set "$branch" paused_by "" usage_resume_at ""
|
|
2088
|
-
registry_set "$branch" resume_kind ""
|
|
2089
|
-
registry_set "$branch" park_loop_cycles 0
|
|
2064
|
+
hr_remote_record_init "$REGISTRY" "$branch" "$worktree" "$log_path" "$engine_kind"
|
|
2090
2065
|
|
|
2091
2066
|
log "dispatching '$branch' (engine=$engine_kind) to GitHub Actions via remote-run.sh (log: $log_path)"
|
|
2092
2067
|
out="$(bash "$REMOTE_RUN" dispatch "$branch" --engine "$engine_kind" --resume none --chain 0 --repo "$MAIN_REPO" 2>&1)"
|
|
@@ -2228,7 +2203,7 @@ classify_run_exit() {
|
|
|
2228
2203
|
registry_set "$branch" pause_reason "$reason" status paused
|
|
2229
2204
|
log "run '$branch' paused (PAUSE honored, reason $reason) — rc=$rc"
|
|
2230
2205
|
if [ "$reason" = "user" ]; then
|
|
2231
|
-
notify paused "$branch" "$log_path" "paused as you asked — run /autonomous-sdlc-harness:branch-resume $branch to continue"
|
|
2206
|
+
notify paused "$branch" "$log_path" "paused as you asked — run /autonomous-sdlc-harness:branch-resume $branch to continue; $(hr_github_resume_route "$branch" "$(registry_get "$branch" engine)")"
|
|
2232
2207
|
fi
|
|
2233
2208
|
return 0
|
|
2234
2209
|
fi
|
|
@@ -2319,7 +2294,7 @@ classify_run_exit() {
|
|
|
2319
2294
|
registry_set "$branch" status park_loop
|
|
2320
2295
|
log "run '$branch' park loop — $cycles consecutive resumes made no progress; not resuming it again (clear: $clar_dir/PARK_LOOP_CLEAR)"
|
|
2321
2296
|
if [ "$JOB_MODE" = "1" ]; then
|
|
2322
|
-
notify park_loop "$branch" "$log_path" "$cycles no-progress resumes — run /autonomous-sdlc-harness:branch-status $branch to see the question, then clear the park loop"
|
|
2297
|
+
notify park_loop "$branch" "$log_path" "$cycles no-progress resumes — run /autonomous-sdlc-harness:branch-status $branch to see the question, then clear the park loop; $(hr_github_answer_route "$branch" "$(registry_get "$branch" engine)" clear)"
|
|
2323
2298
|
else
|
|
2324
2299
|
notify park_loop "$branch" "$log_path" "$cycles no-progress resumes — create $clar_dir/PARK_LOOP_CLEAR to clear"
|
|
2325
2300
|
fi
|
|
@@ -2334,7 +2309,7 @@ classify_run_exit() {
|
|
|
2334
2309
|
registry_set "$branch" status parked
|
|
2335
2310
|
log "run '$branch' parked (clarification waiting) — rc=$rc"
|
|
2336
2311
|
if [ "$JOB_MODE" = "1" ]; then
|
|
2337
|
-
notify parked "$branch" "$log_path" "answer with /autonomous-sdlc-harness:branch-answer $branch"
|
|
2312
|
+
notify parked "$branch" "$log_path" "answer with /autonomous-sdlc-harness:branch-answer $branch; $(hr_github_answer_route "$branch" "$(registry_get "$branch" engine)")"
|
|
2338
2313
|
else
|
|
2339
2314
|
notify parked "$branch" "$log_path" "See $clar_dir"
|
|
2340
2315
|
fi
|
|
@@ -2419,7 +2394,8 @@ begin_park_resume() {
|
|
|
2419
2394
|
# Returns 0 when it resumed, 1 when there was nothing to do, 10 when it deferred
|
|
2420
2395
|
# for the cap and 11 when it deferred for the kill switch — the same three-way
|
|
2421
2396
|
# vocabulary the inbox pass returns, so a caller that already distinguishes them
|
|
2422
|
-
# needs no second one. A remote record returns
|
|
2397
|
+
# needs no second one. A remote record returns 1 and logs nothing: its answers
|
|
2398
|
+
# reach GitHub through the user's own command (REMOTE DISPATCH).
|
|
2423
2399
|
#
|
|
2424
2400
|
# THE PARK IS THE UNIT. A partly answered park is not resumed, and a resume
|
|
2425
2401
|
# consumes every answered pair at once: resuming on a subset would leave pairs
|
|
@@ -2433,6 +2409,7 @@ begin_park_resume() {
|
|
|
2433
2409
|
resume_parked_run() {
|
|
2434
2410
|
local branch="$1"
|
|
2435
2411
|
local worktree log_path
|
|
2412
|
+
record_is_remote "$branch" && return 1
|
|
2436
2413
|
worktree="$(registry_get "$branch" worktree)"
|
|
2437
2414
|
log_path="$(registry_get "$branch" log_path)"
|
|
2438
2415
|
[ -n "$worktree" ] || return 1
|
|
@@ -2467,13 +2444,6 @@ resume_parked_run() {
|
|
|
2467
2444
|
|
|
2468
2445
|
[ -n "$log_path" ] || log_path="$LOGS_DIR/$branch.log"
|
|
2469
2446
|
|
|
2470
|
-
# A remote run is relayed, not re-launched: no cap slot, no lane (REMOTE
|
|
2471
|
-
# DISPATCH). The pairs stay in the mirror, which the next `sync` replaces.
|
|
2472
|
-
if record_is_remote "$branch"; then
|
|
2473
|
-
relay_remote_answers "$branch" "$clar_dir" "$log_path" "$answered_set"
|
|
2474
|
-
return
|
|
2475
|
-
fi
|
|
2476
|
-
|
|
2477
2447
|
local current
|
|
2478
2448
|
current="$(running_count)"
|
|
2479
2449
|
if [ "$current" -ge "$MAX_PARALLEL_RUNS" ]; then
|
|
@@ -2503,48 +2473,6 @@ resume_parked_run() {
|
|
|
2503
2473
|
return 0
|
|
2504
2474
|
}
|
|
2505
2475
|
|
|
2506
|
-
# remote_relay_call <log_path> <remote-run.sh argument>...
|
|
2507
|
-
#
|
|
2508
|
-
# One relay's `remote-run.sh` call, from the main checkout, its output appended
|
|
2509
|
-
# to <log_path>. Returns the script's exit code (0 sent, 2 refused, 3 `gh`
|
|
2510
|
-
# failed) and leaves its first output line in REMOTE_RELAY_FIRST for the
|
|
2511
|
-
# caller's one log line.
|
|
2512
|
-
remote_relay_call() {
|
|
2513
|
-
local log_path="$1" out rc
|
|
2514
|
-
shift
|
|
2515
|
-
out="$(bash "$REMOTE_RUN" "$@" --repo "$MAIN_REPO" 2>&1)"
|
|
2516
|
-
rc=$?
|
|
2517
|
-
[ -z "$out" ] || printf '%s\n' "$out" >>"$log_path"
|
|
2518
|
-
REMOTE_RELAY_FIRST="$(printf '%s\n' "$out" | sed -n '1p')"
|
|
2519
|
-
return "$rc"
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
# relay_remote_answers <branch> <clar_dir> <log_path> <answered_set>
|
|
2523
|
-
#
|
|
2524
|
-
# resume_parked_run's remote arm: the answered set sent as a chain-0 `--resume
|
|
2525
|
-
# answer` dispatch, with `--park-loop-clear` while clear_park_loops has left
|
|
2526
|
-
# `park_loop_clear_pending` set. 0 dispatched; 1 refused or `gh` failed, the
|
|
2527
|
-
# record left `parked` and every file where it is, so the next pass retries.
|
|
2528
|
-
relay_remote_answers() {
|
|
2529
|
-
local branch="$1" clar_dir="$2" log_path="$3" answered_set="$4" rc
|
|
2530
|
-
local -a plc=()
|
|
2531
|
-
[ "$(registry_get "$branch" park_loop_clear_pending)" = "1" ] && plc=(--park-loop-clear)
|
|
2532
|
-
remote_relay_call "$log_path" dispatch "$branch" --engine "$(registry_get "$branch" engine)" \
|
|
2533
|
-
--resume answer --answers-from "$clar_dir" --indexes "$answered_set" ${plc[@]+"${plc[@]}"} --chain 0
|
|
2534
|
-
rc=$?
|
|
2535
|
-
if [ "$rc" -ne 0 ]; then
|
|
2536
|
-
log "remote-run.sh dispatch --resume answer for '$branch' failed (exit $rc): ${REMOTE_RELAY_FIRST:-no output} — leaving it parked"
|
|
2537
|
-
return 1
|
|
2538
|
-
fi
|
|
2539
|
-
log "relayed the answers ($answered_set) of parked remote run '$branch' to GitHub Actions"
|
|
2540
|
-
registry_set "$branch" status running
|
|
2541
|
-
registry_set "$branch" resumed_at "$(date '+%Y-%m-%dT%H:%M:%S')"
|
|
2542
|
-
registry_set "$branch" resume_kind answer
|
|
2543
|
-
registry_set "$branch" park_loop_clear_pending ""
|
|
2544
|
-
notify resumed "$branch" "$log_path" "answered clarification(s) #$answered_set, dispatched"
|
|
2545
|
-
return 0
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
2476
|
# Every `parked` record, offered to the resume above. The kill switch skips the
|
|
2549
2477
|
# WHOLE pass rather than each record, so an operator's brake costs one log line
|
|
2550
2478
|
# in tick() instead of one per parked branch; the cap is per-record, because a
|
|
@@ -2570,14 +2498,15 @@ EOF
|
|
|
2570
2498
|
# run's working copy; the file is removed so the clear is spent once. Answering a
|
|
2571
2499
|
# question never releases the hold — only this file does. A missing working copy
|
|
2572
2500
|
# or an unresolvable state directory leaves the record held. A remote record is
|
|
2573
|
-
#
|
|
2574
|
-
#
|
|
2501
|
+
# skipped: its hold is cleared by the user's own answer or resume command, which
|
|
2502
|
+
# sends `--park-loop-clear` to the job (REMOTE DISPATCH).
|
|
2575
2503
|
clear_park_loops() {
|
|
2576
2504
|
registry_init
|
|
2577
2505
|
local b worktree state_rel clear_file
|
|
2578
2506
|
while IFS= read -r b; do
|
|
2579
2507
|
[ -n "$b" ] || continue
|
|
2580
2508
|
[ "$(registry_get "$b" status)" = "park_loop" ] || continue
|
|
2509
|
+
record_is_remote "$b" && continue
|
|
2581
2510
|
worktree="$(registry_get "$b" worktree)"
|
|
2582
2511
|
[ -n "$worktree" ] || continue
|
|
2583
2512
|
[ -d "$worktree" ] || {
|
|
@@ -2593,7 +2522,6 @@ clear_park_loops() {
|
|
|
2593
2522
|
rm -f "$clear_file"
|
|
2594
2523
|
registry_set "$b" park_loop_cycles 0
|
|
2595
2524
|
registry_set "$b" status parked
|
|
2596
|
-
record_is_remote "$b" && registry_set "$b" park_loop_clear_pending 1
|
|
2597
2525
|
log "park loop cleared for '$b' (PARK_LOOP_CLEAR found) — back to parked"
|
|
2598
2526
|
done <<EOF
|
|
2599
2527
|
$(registry_branches)
|
|
@@ -2645,12 +2573,12 @@ begin_pause_resume() {
|
|
|
2645
2573
|
#
|
|
2646
2574
|
# Resume one paused run if a RESUME trigger has landed in its working copy.
|
|
2647
2575
|
# Return codes, the missing-working-copy outcome and the kill-switch/cap ordering
|
|
2648
|
-
# are resume_parked_run's, for the same reasons. A remote record
|
|
2649
|
-
#
|
|
2650
|
-
# nothing removed.
|
|
2576
|
+
# are resume_parked_run's, for the same reasons. A remote record returns 1 and
|
|
2577
|
+
# logs nothing, as there.
|
|
2651
2578
|
resume_paused_run() {
|
|
2652
2579
|
local branch="$1"
|
|
2653
2580
|
local worktree log_path
|
|
2581
|
+
record_is_remote "$branch" && return 1
|
|
2654
2582
|
worktree="$(registry_get "$branch" worktree)"
|
|
2655
2583
|
log_path="$(registry_get "$branch" log_path)"
|
|
2656
2584
|
[ -n "$worktree" ] || return 1
|
|
@@ -2681,23 +2609,6 @@ resume_paused_run() {
|
|
|
2681
2609
|
|
|
2682
2610
|
[ -n "$log_path" ] || log_path="$LOGS_DIR/$branch.log"
|
|
2683
2611
|
|
|
2684
|
-
# A remote run is relayed, not re-launched: no cap slot, no lane (REMOTE
|
|
2685
|
-
# DISPATCH). The sentinels go only once the dispatch was sent.
|
|
2686
|
-
if record_is_remote "$branch"; then
|
|
2687
|
-
local rc
|
|
2688
|
-
remote_relay_call "$log_path" dispatch "$branch" --engine "$(registry_get "$branch" engine)" \
|
|
2689
|
-
--resume pause --chain 0
|
|
2690
|
-
rc=$?
|
|
2691
|
-
if [ "$rc" -ne 0 ]; then
|
|
2692
|
-
log "remote-run.sh dispatch --resume pause for '$branch' failed (exit $rc): ${REMOTE_RELAY_FIRST:-no output} — leaving it paused"
|
|
2693
|
-
return 1
|
|
2694
|
-
fi
|
|
2695
|
-
log "relayed the RESUME of paused remote run '$branch' to GitHub Actions"
|
|
2696
|
-
begin_pause_resume "$branch" "$state_abs"
|
|
2697
|
-
notify resumed "$branch" "$log_path" "after pause, dispatched"
|
|
2698
|
-
return 0
|
|
2699
|
-
fi
|
|
2700
|
-
|
|
2701
2612
|
local current
|
|
2702
2613
|
current="$(running_count)"
|
|
2703
2614
|
if [ "$current" -ge "$MAX_PARALLEL_RUNS" ]; then
|
|
@@ -2740,39 +2651,6 @@ $(registry_branches)
|
|
|
2740
2651
|
EOF
|
|
2741
2652
|
}
|
|
2742
2653
|
|
|
2743
|
-
# THE PAUSE RELAY (REMOTE DISPATCH). A `running` remote record whose mirror
|
|
2744
|
-
# holds `<state_dir>/PAUSE` — a user's pause — is sent `remote-run.sh pause`;
|
|
2745
|
-
# on exit 0 the mirror's PAUSE goes and `pause_relayed_at` is stamped, and on
|
|
2746
|
-
# any other exit both stay for the next pass. Called from `tick` ahead of the
|
|
2747
|
-
# kill switch, because a pause starts nothing; it consults neither the cap nor
|
|
2748
|
-
# the lane.
|
|
2749
|
-
relay_remote_pauses() {
|
|
2750
|
-
registry_init
|
|
2751
|
-
local b wt state_rel rc log_path
|
|
2752
|
-
while IFS= read -r b; do
|
|
2753
|
-
[ -n "$b" ] || continue
|
|
2754
|
-
record_is_remote "$b" || continue
|
|
2755
|
-
[ "$(registry_get "$b" status)" = "running" ] || continue
|
|
2756
|
-
wt="$(registry_get "$b" worktree)"
|
|
2757
|
-
[ -n "$wt" ] && [ -d "$wt" ] || continue
|
|
2758
|
-
state_rel="$(run_state_dir "$wt")" || continue
|
|
2759
|
-
[ -f "$wt/$state_rel/PAUSE" ] || continue
|
|
2760
|
-
log_path="$(registry_get "$b" log_path)"
|
|
2761
|
-
[ -n "$log_path" ] || log_path="$LOGS_DIR/$b.log"
|
|
2762
|
-
remote_relay_call "$log_path" pause "$b"
|
|
2763
|
-
rc=$?
|
|
2764
|
-
if [ "$rc" -ne 0 ]; then
|
|
2765
|
-
log "remote-run.sh pause for '$b' failed (exit $rc): ${REMOTE_RELAY_FIRST:-no output} — PAUSE left in the mirror"
|
|
2766
|
-
continue
|
|
2767
|
-
fi
|
|
2768
|
-
rm -f "$wt/$state_rel/PAUSE"
|
|
2769
|
-
registry_set "$b" pause_relayed_at "$(date +%s)"
|
|
2770
|
-
log "relayed the PAUSE of remote run '$b' to GitHub Actions"
|
|
2771
|
-
done <<EOF
|
|
2772
|
-
$(registry_branches)
|
|
2773
|
-
EOF
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
2654
|
# -----------------------------------------------------------------------------
|
|
2777
2655
|
# THE STALENESS WATCHDOG. The header states what this pass heals that the
|
|
2778
2656
|
# reconcile pass cannot see, why a stale mtime ALONE never kills, why the
|
|
@@ -3021,29 +2899,27 @@ reject_preserving_status() {
|
|
|
3021
2899
|
# remote_commit_and_push <branch> <worktree> <log_path> <file> <fname> <dest> <rel> <subject> <what>
|
|
3022
2900
|
#
|
|
3023
2901
|
# The remote arm's commit-and-push, BLOCKING (REMOTE DISPATCH in the header):
|
|
3024
|
-
# 0 = <
|
|
2902
|
+
# 0 = <rel> is committed and origin/<branch> carries HEAD; 1 = it is not, and
|
|
3025
2903
|
# fail_before_launch has already run, so the caller dispatches nothing. The
|
|
3026
|
-
#
|
|
3027
|
-
#
|
|
2904
|
+
# library's staging, identical-re-drop skip and landed test (THE ARTIFACT
|
|
2905
|
+
# PLACEMENT in lib/harness-run-lib.sh) with the local arm's wrappers; the push
|
|
2906
|
+
# stays a separate statement from the commit. <dest> is the caller's.
|
|
3028
2907
|
remote_commit_and_push() {
|
|
3029
2908
|
local branch="$1" worktree="$2" log_path="$3" file="$4" fname="$5"
|
|
3030
|
-
local dest="$6" rel="$7" subject="$8" what="$9"
|
|
2909
|
+
local dest="$6" rel="$7" subject="$8" what="$9" rc=0
|
|
3031
2910
|
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
log "the $what for '$branch' is already committed (identical re-drop) — skipping the commit, pushing anyway"
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
2911
|
+
hr_commit_placed "$COMMIT_ON_BRANCH" "$worktree" "$rel" "$subject" >>"$log_path" 2>&1 || rc=$?
|
|
2912
|
+
case "$rc" in
|
|
2913
|
+
3) log "the $what for '$branch' is already committed (identical re-drop) — skipping the commit, pushing anyway" ;;
|
|
2914
|
+
0) log "committed the $what for '$branch' ($subject)" ;;
|
|
2915
|
+
*)
|
|
2916
|
+
log "commit-on-branch.sh could not commit the $what for '$branch' — not dispatching (see $log_path)"
|
|
2917
|
+
fail_before_launch "$branch" "$file" "$fname" "(commit-on-branch.sh could not commit the $what — nothing dispatched; see $log_path)"
|
|
2918
|
+
return 1
|
|
2919
|
+
;;
|
|
2920
|
+
esac
|
|
3042
2921
|
|
|
3043
|
-
"$PUSH_BRANCH" "$worktree" >>"$log_path" 2>&1
|
|
3044
|
-
head="$(git -C "$worktree" rev-parse --verify --quiet HEAD)" || head=""
|
|
3045
|
-
upstream="$(git -C "$worktree" rev-parse --verify --quiet "refs/remotes/origin/$branch")" || upstream=""
|
|
3046
|
-
if [ -z "$head" ] || [ "$head" != "$upstream" ]; then
|
|
2922
|
+
if ! hr_push_landed "$PUSH_BRANCH" "$worktree" "$branch" >>"$log_path" 2>&1; then
|
|
3047
2923
|
log "push-branch.sh did not bring origin/$branch to HEAD after the $what for '$branch' — not dispatching (see $log_path)"
|
|
3048
2924
|
fail_before_launch "$branch" "$file" "$fname" "(push-branch.sh did not push the $what to origin/$branch — nothing dispatched; the job checks out origin/$branch; see $log_path)"
|
|
3049
2925
|
return 1
|
|
@@ -3065,18 +2941,8 @@ remote_commit_and_push() {
|
|
|
3065
2941
|
# ^(.+)_review(_[0-9]+)?\.md$ -> user_review engine, reuse-else-recreate
|
|
3066
2942
|
# ^(.+)_docs\.md$ -> docs engine, a fresh working copy
|
|
3067
2943
|
#
|
|
3068
|
-
# The
|
|
3069
|
-
#
|
|
3070
|
-
# cannot end in more than one of `_task_prompt.md` / `_review[_<n>].md` /
|
|
3071
|
-
# `_docs.md`, so a branch whose own name contains `review` or `task_prompt`
|
|
3072
|
-
# cannot be mis-routed — `foo_review_task_prompt.md` is the task engine on branch
|
|
3073
|
-
# `foo_review`, and `foo_task_prompt_review.md` is the review engine on branch
|
|
3074
|
-
# `foo_task_prompt`. POSIX leftmost-longest matching of the greedy `(.+)` derives
|
|
3075
|
-
# the right branch from a round-suffixed name: `foo_review_2.md` -> branch `foo`
|
|
3076
|
-
# (the `_2` is consumed by the optional `(_[0-9]+)?`), while
|
|
3077
|
-
# `foo_review_2_review.md` -> branch `foo_review_2`. THE WATCHER DERIVES ONLY THE
|
|
3078
|
-
# BRANCH, never the round: the engine resolves the latest round itself, inside
|
|
3079
|
-
# the working copy, which is why nothing here has to remember one.
|
|
2944
|
+
# The library owns these patterns and their order: `hr_inbox_route_var` in
|
|
2945
|
+
# lib/harness-run-lib.sh, whose comment states why they cannot mis-route.
|
|
3080
2946
|
#
|
|
3081
2947
|
# A filename matching none of the three is logged and ARCHIVED rather than left
|
|
3082
2948
|
# where it is, so it is not re-logged on every pass for as long as the watcher
|
|
@@ -3089,24 +2955,13 @@ process_inbox_file() {
|
|
|
3089
2955
|
|
|
3090
2956
|
# (1) Route the filename to its pairing and derive <branch> — see above.
|
|
3091
2957
|
local branch engine_kind
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
branch="$(printf '%s' "$fname" | sed -nE 's/^(.+)_review(_[0-9]+)?\.md$/\1/p')"
|
|
3097
|
-
if [ -n "$branch" ]; then
|
|
3098
|
-
engine_kind="user_review"
|
|
3099
|
-
else
|
|
3100
|
-
branch="$(printf '%s' "$fname" | sed -nE 's/^(.+)_docs\.md$/\1/p')"
|
|
3101
|
-
if [ -n "$branch" ]; then
|
|
3102
|
-
engine_kind="docs"
|
|
3103
|
-
else
|
|
3104
|
-
log "rejecting '$fname': not a <branch>_task_prompt.md / <branch>_review[_<n>].md / <branch>_docs.md file — skipping"
|
|
3105
|
-
mv "$file" "$ARCHIVE_DIR/rejected_$(date '+%Y%m%d%H%M%S')_$fname" 2>/dev/null || rm -f "$file"
|
|
3106
|
-
return 0
|
|
3107
|
-
fi
|
|
3108
|
-
fi
|
|
2958
|
+
if ! hr_inbox_route_var "$fname"; then
|
|
2959
|
+
log "rejecting '$fname': not a <branch>_task_prompt.md / <branch>_review[_<n>].md / <branch>_docs.md file — skipping"
|
|
2960
|
+
mv "$file" "$ARCHIVE_DIR/rejected_$(date '+%Y%m%d%H%M%S')_$fname" 2>/dev/null || rm -f "$file"
|
|
2961
|
+
return 0
|
|
3109
2962
|
fi
|
|
2963
|
+
branch="$HR_INBOX_BRANCH"
|
|
2964
|
+
engine_kind="$HR_INBOX_KIND"
|
|
3110
2965
|
|
|
3111
2966
|
# The central log every step below appends to. A branch derived from a FILENAME
|
|
3112
2967
|
# cannot contain a `/`, so this name needs no sanitizing — unlike the
|
|
@@ -3276,10 +3131,11 @@ process_inbox_file() {
|
|
|
3276
3131
|
|
|
3277
3132
|
# (3a) Copy the dropped prompt into the working copy, then archive the inbox
|
|
3278
3133
|
# file so it is not processed again.
|
|
3279
|
-
local prompt_rel=
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3134
|
+
local prompt_rel prompt_dest prompt_subject prompt_rc=0
|
|
3135
|
+
prompt_rel="$(hr_task_prompt_rel "$state_rel" "$branch")"
|
|
3136
|
+
prompt_dest="$worktree/$prompt_rel"
|
|
3137
|
+
prompt_subject="$(hr_task_prompt_subject "$branch")"
|
|
3138
|
+
hr_place_artifact "$worktree" "$file" "$prompt_rel"
|
|
3283
3139
|
mv "$file" "$ARCHIVE_DIR/$(date '+%Y%m%d%H%M%S')_$fname" 2>/dev/null || rm -f "$file"
|
|
3284
3140
|
log "copied the prompt -> $prompt_dest; archived the inbox file"
|
|
3285
3141
|
|
|
@@ -3294,13 +3150,14 @@ process_inbox_file() {
|
|
|
3294
3150
|
#
|
|
3295
3151
|
# Only the prompt is staged, by explicit path — never `git add -A` or
|
|
3296
3152
|
# `git add .`, matching the no-blanket-add rule every unattended commit point
|
|
3297
|
-
# in this family follows. The
|
|
3153
|
+
# in this family follows. The nothing-staged pre-check is what makes
|
|
3298
3154
|
# an identical re-drop of an already-committed prompt a no-op instead of an
|
|
3299
3155
|
# empty commit; when there IS a diff, the WRAPPER does the real staging and
|
|
3300
3156
|
# the commit, so this commit point inherits its protected-branch refusal
|
|
3301
3157
|
# rather than re-implementing it. The wrapper stages paths RELATIVE TO THE
|
|
3302
|
-
# REPOSITORY TOP, so it is handed the repo-relative path
|
|
3303
|
-
#
|
|
3158
|
+
# REPOSITORY TOP, so it is handed the repo-relative path. The placement
|
|
3159
|
+
# itself lives in lib/harness-run-lib.sh (THE ARTIFACT PLACEMENT), shared
|
|
3160
|
+
# with a job that starts a run.
|
|
3304
3161
|
#
|
|
3305
3162
|
# A FAILURE AT EITHER STEP IS LOGGED AND THE RUN LAUNCHES ANYWAY: a prompt
|
|
3306
3163
|
# commit that did not land has to be VISIBLE, and it must never be the reason
|
|
@@ -3311,20 +3168,18 @@ process_inbox_file() {
|
|
|
3311
3168
|
# dispatch instead (REMOTE DISPATCH in the header).
|
|
3312
3169
|
if [ "$remote" = 1 ]; then
|
|
3313
3170
|
remote_commit_and_push "$branch" "$worktree" "$log_path" "$file" "$fname" \
|
|
3314
|
-
"$prompt_dest" "$prompt_rel" "
|
|
3171
|
+
"$prompt_dest" "$prompt_rel" "$prompt_subject" "task prompt" || return 0
|
|
3315
3172
|
else
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
log "the task prompt for '$branch' is already committed (identical re-drop) — skipping the commit"
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
log "WARNING: could not commit the task prompt for '$branch' — launching anyway (its working-tree-clean precondition may be dishonest; see $log_path)"
|
|
3327
|
-
fi
|
|
3173
|
+
hr_commit_placed "$COMMIT_ON_BRANCH" "$worktree" "$prompt_rel" "$prompt_subject" >>"$log_path" 2>&1 || prompt_rc=$?
|
|
3174
|
+
case "$prompt_rc" in
|
|
3175
|
+
3) log "the task prompt for '$branch' is already committed (identical re-drop) — skipping the commit" ;;
|
|
3176
|
+
0)
|
|
3177
|
+
log "committed the task prompt for '$branch' ($prompt_subject)"
|
|
3178
|
+
"$PUSH_BRANCH" "$worktree" >>"$log_path" 2>&1 ||
|
|
3179
|
+
log "WARNING: push-branch.sh failed after the task-prompt commit for '$branch' — continuing"
|
|
3180
|
+
;;
|
|
3181
|
+
*) log "WARNING: could not commit the task prompt for '$branch' — launching anyway (its working-tree-clean precondition may be dishonest; see $log_path)" ;;
|
|
3182
|
+
esac
|
|
3328
3183
|
fi
|
|
3329
3184
|
elif [ "$engine_kind" = "docs" ]; then
|
|
3330
3185
|
# (2c) Docs path: the task path's strategy exactly — a FRESH working copy off
|
|
@@ -3352,26 +3207,24 @@ process_inbox_file() {
|
|
|
3352
3207
|
# rationale is stated once, above.
|
|
3353
3208
|
local docs_rel="$state_rel/docs_catalog/${branch}_docs.md"
|
|
3354
3209
|
local docs_dest="$worktree/$docs_rel"
|
|
3355
|
-
|
|
3356
|
-
|
|
3210
|
+
local docs_subject="chore: add docs checklist for $branch" docs_rc=0
|
|
3211
|
+
hr_place_artifact "$worktree" "$file" "$docs_rel"
|
|
3357
3212
|
mv "$file" "$ARCHIVE_DIR/$(date '+%Y%m%d%H%M%S')_$fname" 2>/dev/null || rm -f "$file"
|
|
3358
3213
|
log "copied the docs checklist -> $docs_dest; archived the inbox file"
|
|
3359
3214
|
if [ "$remote" = 1 ]; then
|
|
3360
3215
|
remote_commit_and_push "$branch" "$worktree" "$log_path" "$file" "$fname" \
|
|
3361
|
-
"$docs_dest" "$docs_rel" "
|
|
3216
|
+
"$docs_dest" "$docs_rel" "$docs_subject" "docs checklist" || return 0
|
|
3362
3217
|
else
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
log "the docs checklist for '$branch' is already committed (identical re-drop) — skipping the commit"
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
log "WARNING: could not commit the docs checklist for '$branch' — launching anyway (see $log_path)"
|
|
3374
|
-
fi
|
|
3218
|
+
hr_commit_placed "$COMMIT_ON_BRANCH" "$worktree" "$docs_rel" "$docs_subject" >>"$log_path" 2>&1 || docs_rc=$?
|
|
3219
|
+
case "$docs_rc" in
|
|
3220
|
+
3) log "the docs checklist for '$branch' is already committed (identical re-drop) — skipping the commit" ;;
|
|
3221
|
+
0)
|
|
3222
|
+
log "committed the docs checklist for '$branch' ($docs_subject)"
|
|
3223
|
+
"$PUSH_BRANCH" "$worktree" >>"$log_path" 2>&1 ||
|
|
3224
|
+
log "WARNING: push-branch.sh failed after the docs-checklist commit for '$branch' — continuing"
|
|
3225
|
+
;;
|
|
3226
|
+
*) log "WARNING: could not commit the docs checklist for '$branch' — launching anyway (see $log_path)" ;;
|
|
3227
|
+
esac
|
|
3375
3228
|
fi
|
|
3376
3229
|
else
|
|
3377
3230
|
# (2b) Review path: REUSE the branch's existing working copy when it is
|
|
@@ -3464,7 +3317,7 @@ process_inbox_file() {
|
|
|
3464
3317
|
log "copied the review -> $review_dest; archived the inbox file"
|
|
3465
3318
|
if [ "$remote" = 1 ]; then
|
|
3466
3319
|
remote_commit_and_push "$branch" "$worktree" "$log_path" "$file" "$fname" \
|
|
3467
|
-
"$review_dest" "$review_rel" "
|
|
3320
|
+
"$review_dest" "$review_rel" "$(hr_user_review_subject "$branch")" "user review" || return 0
|
|
3468
3321
|
fi
|
|
3469
3322
|
fi
|
|
3470
3323
|
|
|
@@ -3733,8 +3586,7 @@ usage_gate() {
|
|
|
3733
3586
|
local b status pb ra wt lp state_rel="" state_abs=""
|
|
3734
3587
|
while IFS= read -r b; do
|
|
3735
3588
|
[ -n "$b" ] || continue
|
|
3736
|
-
# The auto-resume side skips a remote run: its job gates itself
|
|
3737
|
-
# resume_paused_run relays only a user's RESUME.
|
|
3589
|
+
# The auto-resume side skips a remote run: its job gates itself.
|
|
3738
3590
|
record_is_remote "$b" && continue
|
|
3739
3591
|
status="$(registry_get "$b" status)"
|
|
3740
3592
|
pb="$(registry_get "$b" paused_by)"
|
|
@@ -3919,11 +3771,8 @@ EOF
|
|
|
3919
3771
|
}
|
|
3920
3772
|
|
|
3921
3773
|
# -----------------------------------------------------------------------------
|
|
3922
|
-
# One pass. The
|
|
3923
|
-
#
|
|
3924
|
-
# one action that still runs under the brake, because it starts nothing (see
|
|
3925
|
-
# REMOTE DISPATCH) — then the reconcile that frees capacity for the passes that
|
|
3926
|
-
# read the cap.
|
|
3774
|
+
# One pass. The kill switch first, so an operator's brake beats everything
|
|
3775
|
+
# else, then the reconcile that frees capacity for the passes that read the cap.
|
|
3927
3776
|
# -----------------------------------------------------------------------------
|
|
3928
3777
|
tick() {
|
|
3929
3778
|
# Drop the library's per-process cache so an edit to `harness.config.json` is
|
|
@@ -3933,8 +3782,6 @@ tick() {
|
|
|
3933
3782
|
hr_config_reset
|
|
3934
3783
|
hr_config_load "$MAIN_REPO" || :
|
|
3935
3784
|
|
|
3936
|
-
relay_remote_pauses
|
|
3937
|
-
|
|
3938
3785
|
if kill_switch_active; then
|
|
3939
3786
|
log "global kill switch active — not launching or resuming runs this pass"
|
|
3940
3787
|
# A braked watcher must not sit on the machine-level lane: it is starting
|
|
@@ -4340,14 +4187,14 @@ run_job() {
|
|
|
4340
4187
|
# run over rather than waiting on nothing.
|
|
4341
4188
|
decision=wait-poller
|
|
4342
4189
|
detail="usage limit reached; the in-job usage wait passed its bound (REMOTE_WAIT_MAX_SECS=${REMOTE_WAIT_MAX_SECS}s past the reset) without a resume"
|
|
4343
|
-
notify paused "$branch" "$log_path" "usage limit: the in-job wait passed ${REMOTE_WAIT_MAX_SECS}s after the reset without a resume — run /autonomous-sdlc-harness:branch-resume $branch to continue"
|
|
4190
|
+
notify paused "$branch" "$log_path" "usage limit: the in-job wait passed ${REMOTE_WAIT_MAX_SECS}s after the reset without a resume — run /autonomous-sdlc-harness:branch-resume $branch to continue; $(hr_github_resume_route "$branch" "$(registry_get "$branch" engine)")"
|
|
4344
4191
|
break
|
|
4345
4192
|
fi
|
|
4346
4193
|
;;
|
|
4347
4194
|
*)
|
|
4348
4195
|
job_auto_resume "$branch" "$worktree" "$log_path" "$state_abs" "an overload self-pause" && continue
|
|
4349
4196
|
detail="paused itself on API overload; automatic resumes exhausted"
|
|
4350
|
-
notify paused "$branch" "$log_path" "paused itself on API overload — run /autonomous-sdlc-harness:branch-resume $branch to continue"
|
|
4197
|
+
notify paused "$branch" "$log_path" "paused itself on API overload — run /autonomous-sdlc-harness:branch-resume $branch to continue; $(hr_github_resume_route "$branch" "$(registry_get "$branch" engine)")"
|
|
4351
4198
|
break
|
|
4352
4199
|
;;
|
|
4353
4200
|
esac
|