autonomous-sdlc-harness 0.6.2 → 0.6.3
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 +1 -1
- 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 +7 -6
- package/dist/doctor/checks.js.map +1 -1
- package/dist/remote/githubActions.js +3 -2
- package/dist/remote/githubActions.js.map +1 -1
- package/package.json +1 -1
- package/templates/github/workflows/harness-resume.yml +6 -5
- package/templates/github/workflows/harness-run.yml +29 -7
- package/templates/scripts/autonomous-watcher.sh +121 -21
- package/templates/scripts/lib/harness-run-lib.sh +140 -13
- package/templates/scripts/push-branch.sh +69 -11
- package/templates/scripts/remote-run.sh +1249 -242
- package/templates/state-dir/user_reviews/README.md +1 -1
|
@@ -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
|
|
@@ -28,7 +28,13 @@
|
|
|
28
28
|
# must NEVER abort the calling run — a stale remote is recoverable and a halted
|
|
29
29
|
# run is not. That is why this script uses `set -uo pipefail` deliberately
|
|
30
30
|
# WITHOUT `set -e`, and why even the fail-closed arm below exits 0: it refuses
|
|
31
|
-
# to push, which is the closed outcome here, and says so.
|
|
31
|
+
# to push, which is the closed outcome here, and says so. A push the remote
|
|
32
|
+
# refused for any reason but a `[rejected]` ref is attempted up to
|
|
33
|
+
# PUSH_ATTEMPTS times in all — waiting PUSH_RETRY_DELAY_SECS before the second
|
|
34
|
+
# attempt and three times that before the third — and is still never fatal once
|
|
35
|
+
# given up. PUSH_RETRY_DELAY_SECS is read from the environment as a test and
|
|
36
|
+
# tuning seam: default 5, and anything but a non-negative integer falls back to
|
|
37
|
+
# 5 with one line.
|
|
32
38
|
#
|
|
33
39
|
# DEFENCE IN DEPTH, NOT THE SOLE GUARD. The caller-agnostic backstop is the
|
|
34
40
|
# committed pre-push hook `init` writes into the configured `githooksDir`, which
|
|
@@ -52,7 +58,11 @@
|
|
|
52
58
|
#
|
|
53
59
|
# WHAT IT NEVER DOES. It performs only a fast-forward push of already-committed
|
|
54
60
|
# work: no `--force`, no `--force-with-lease`, no history-rewriting flag, no
|
|
55
|
-
# commit of its own, and no non-zero exit.
|
|
61
|
+
# commit of its own, and no non-zero exit. It never retries a push git reports
|
|
62
|
+
# as `! [rejected]` (non-fast-forward, fetch first), and never fetches or
|
|
63
|
+
# rebases: a push that lost a race fails loudly, which is the run-control rule
|
|
64
|
+
# of record (docs/github-run-control.md -> "A push that loses a race fails
|
|
65
|
+
# loudly, and is never fetched, rebased or retried").
|
|
56
66
|
#
|
|
57
67
|
# Usage: push-branch.sh [<repo-dir>]
|
|
58
68
|
# <repo-dir> worktree/repository to operate in (default: $PWD)
|
|
@@ -73,9 +83,35 @@
|
|
|
73
83
|
# -> exit 0, refusal message, nothing pushed
|
|
74
84
|
# push fails git -C "$d" remote set-url origin /nonexistent.git
|
|
75
85
|
# -> exit 0 with a visible failure line
|
|
86
|
+
# refused once printf '%s\n' '#!/bin/sh' 'c="$GIT_DIR/refusals"' \
|
|
87
|
+
# '[ -e "$c" ] && exit 0' ': > "$c"; exit 1' > "$b/hooks/pre-receive"
|
|
88
|
+
# chmod +x "$b/hooks/pre-receive"
|
|
89
|
+
# git -C "$d" commit -qm x --allow-empty
|
|
90
|
+
# PUSH_RETRY_DELAY_SECS=0 push-branch.sh "$d"
|
|
91
|
+
# -> exit 0, one "retrying" line, and `git -C "$b" rev-parse
|
|
92
|
+
# feat_x` equals `git -C "$d" rev-parse HEAD`
|
|
93
|
+
# remote moved c=$(mktemp -d); git clone -q -b feat_x "$b" "$c"
|
|
94
|
+
# git -C "$c" commit -qm other --allow-empty; git -C "$c" push -q
|
|
95
|
+
# git -C "$d" commit -qm mine --allow-empty; push-branch.sh "$d"
|
|
96
|
+
# -> exit 0, a "(not retried)" line, one push attempt
|
|
76
97
|
|
|
77
98
|
set -uo pipefail
|
|
78
99
|
|
|
100
|
+
# Attempts in all, and the wait before the second; the third waits three times
|
|
101
|
+
# that (EVERY FAILURE PATH IS NON-FATAL above).
|
|
102
|
+
PUSH_ATTEMPTS=3
|
|
103
|
+
PUSH_RETRY_DELAY_SECS="${PUSH_RETRY_DELAY_SECS:-5}"
|
|
104
|
+
case "$PUSH_RETRY_DELAY_SECS" in
|
|
105
|
+
'' | *[!0-9]*)
|
|
106
|
+
echo "push-branch.sh: PUSH_RETRY_DELAY_SECS='$PUSH_RETRY_DELAY_SECS' is not a non-negative integer — using 5"
|
|
107
|
+
PUSH_RETRY_DELAY_SECS=5
|
|
108
|
+
;;
|
|
109
|
+
*)
|
|
110
|
+
# Base 10, so a leading zero is not read as octal.
|
|
111
|
+
PUSH_RETRY_DELAY_SECS=$((10#$PUSH_RETRY_DELAY_SECS))
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
|
|
79
115
|
# The library is reached by a path computed from this script's own location — no
|
|
80
116
|
# session root, and no runtime-substituted token, is assumed. Not finding it is
|
|
81
117
|
# non-fatal like everything else here.
|
|
@@ -125,16 +161,38 @@ esac
|
|
|
125
161
|
# set it on the fly, so a branch with no upstream is still pushed. Fast-forward
|
|
126
162
|
# only.
|
|
127
163
|
if git -C "$top" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then
|
|
128
|
-
git -C "$top" push
|
|
164
|
+
push_cmd=(git -C "$top" push)
|
|
129
165
|
else
|
|
130
|
-
git -C "$top" push --set-upstream origin "$branch"
|
|
166
|
+
push_cmd=(git -C "$top" push --set-upstream origin "$branch")
|
|
131
167
|
fi
|
|
132
|
-
push_status=$?
|
|
133
168
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
169
|
+
# Every failure line keeps the `push failed for <branch>` prefix: logs and
|
|
170
|
+
# Gate 12 records quote it.
|
|
171
|
+
attempt=1
|
|
172
|
+
delay="$PUSH_RETRY_DELAY_SECS"
|
|
173
|
+
while :; do
|
|
174
|
+
push_out="$("${push_cmd[@]}" 2>&1)"
|
|
175
|
+
push_status=$?
|
|
176
|
+
# git writes its ref-status lines to stderr; keep them there.
|
|
177
|
+
[ -n "$push_out" ] && printf '%s\n' "$push_out" >&2
|
|
178
|
+
|
|
179
|
+
if [ "$push_status" -eq 0 ]; then
|
|
180
|
+
echo "push-branch.sh: pushed $branch to origin"
|
|
181
|
+
break
|
|
182
|
+
fi
|
|
183
|
+
# A here-string, not a pipe: `grep -q` exiting early would fail a pipeline
|
|
184
|
+
# under pipefail.
|
|
185
|
+
if grep -q '^ ! \[rejected\]' <<<"$push_out"; then
|
|
186
|
+
echo "push-branch.sh: push failed for $branch: origin has commits this branch does not (not retried)"
|
|
187
|
+
break
|
|
188
|
+
fi
|
|
189
|
+
if [ "$attempt" -ge "$PUSH_ATTEMPTS" ]; then
|
|
190
|
+
echo "push-branch.sh: push failed for $branch after $PUSH_ATTEMPTS attempts (see output above)"
|
|
191
|
+
break
|
|
192
|
+
fi
|
|
193
|
+
echo "push-branch.sh: push failed for $branch (attempt $attempt of $PUSH_ATTEMPTS); retrying in ${delay}s"
|
|
194
|
+
sleep "$delay"
|
|
195
|
+
attempt=$((attempt + 1))
|
|
196
|
+
delay=$((delay * 3))
|
|
197
|
+
done
|
|
140
198
|
exit 0
|