autonomous-sdlc-harness 0.4.0 → 0.4.2
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/doctor.js +32 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +8 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/core/defaultBranchPush.js +26 -0
- package/dist/core/defaultBranchPush.js.map +1 -0
- package/dist/core/git.js +35 -3
- package/dist/core/git.js.map +1 -1
- package/dist/doctor/checks.js +148 -54
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/permissionProfile.js +60 -29
- package/dist/generators/permissionProfile.js.map +1 -1
- package/dist/generators/projectSettings.js +2 -1
- package/dist/generators/projectSettings.js.map +1 -1
- package/dist/generators/repoRoot.js +5 -2
- package/dist/generators/repoRoot.js.map +1 -1
- package/dist/retrieval/runtime.js +34 -11
- package/dist/retrieval/runtime.js.map +1 -1
- package/package.json +2 -2
- package/templates/github/workflows/harness-resume.yml +21 -5
- package/templates/github/workflows/harness-run.yml +42 -20
- package/templates/repo/gitignore +6 -1
- package/templates/scripts/autonomous-watcher.sh +183 -52
- package/templates/scripts/lib/harness-run-lib.sh +303 -35
- package/templates/scripts/remote-run.sh +48 -38
- package/templates/state-dir/autonomous_logs/README.md +1 -1
|
@@ -58,21 +58,27 @@
|
|
|
58
58
|
# and are written only by the `hr_lane_*` functions.
|
|
59
59
|
# 2. THE RUN REGISTRY writes the registry file its caller names. Fence: that
|
|
60
60
|
# file is `<root>/<state_dir>/autonomous_logs/registry.json`, resolved
|
|
61
|
-
# through `hr_state_path`,
|
|
62
|
-
# and `
|
|
61
|
+
# through `hr_state_path`, plus the lock directory `<file>.lock` (and its
|
|
62
|
+
# `.stale.*` move-aside and `.break` mutex while a stale one is broken)
|
|
63
|
+
# and the temp files
|
|
64
|
+
# `.registry.*` in the registry's own directory. Written only by
|
|
65
|
+
# `hr_registry_init`, `hr_registry_set` and the `hr_registry_lock` /
|
|
66
|
+
# `hr_registry_unlock` pair `hr_registry_set` calls.
|
|
63
67
|
# 3. THE REMOTE STATE BUNDLE writes the files its format lists. Fence: inside
|
|
64
68
|
# `<root>/<state_dir>/` (resolved through `hr_state_dir`), only
|
|
65
69
|
# `autonomous_logs/remote_status.json`, `clarifications/<branch>/`,
|
|
66
|
-
# `PAUSE_PROGRESS.md`, `.flow_walker_state
|
|
67
|
-
# `autonomous_logs/remote_superseded
|
|
68
|
-
#
|
|
70
|
+
# `PAUSE_PROGRESS.md`, `.flow_walker_state`, the move-aside directory
|
|
71
|
+
# `autonomous_logs/remote_superseded/` and, only where nothing exists yet,
|
|
72
|
+
# files under the eight planning paths `hr_remote_planning_paths` assigns;
|
|
73
|
+
# outside it, only the caller-named `<out_dir>` (its `planning/` included)
|
|
74
|
+
# of `hr_remote_bundle_write` and the caller-named `<out_json>`
|
|
69
75
|
# of `hr_remote_status_write`. Written only by `hr_remote_status_write`,
|
|
70
76
|
# `hr_remote_bundle_write` and `hr_remote_bundle_restore`, and nothing
|
|
71
77
|
# there but a writer's own failed temp file is ever removed.
|
|
72
78
|
#
|
|
73
79
|
# A caller that calls no `hr_lane_*`, `hr_registry_init`, `hr_registry_set`,
|
|
74
|
-
# `
|
|
75
|
-
# `hr_remote_bundle_restore` function still gets a library that only reads. The
|
|
80
|
+
# `hr_registry_lock`, `hr_registry_unlock`, `hr_remote_status_write`,
|
|
81
|
+
# `hr_remote_bundle_write` or `hr_remote_bundle_restore` function still gets a library that only reads. The
|
|
76
82
|
# lane's ceilings are the only environment values here that carry policy, because
|
|
77
83
|
# the lane is machine-scoped and has no configuration key to carry them; each is
|
|
78
84
|
# named where it is used. `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `XDG_CACHE_HOME`,
|
|
@@ -165,7 +171,9 @@
|
|
|
165
171
|
# because callers capture stdout. The one pass-through is the registry's two
|
|
166
172
|
# writers, `hr_registry_init` and `hr_registry_set`, which leave the shell's,
|
|
167
173
|
# `mktemp`'s and `jq`'s own stderr on a failed write to the caller, as the
|
|
168
|
-
# watcher's bodies they replaced did — that stream is the watcher's log
|
|
174
|
+
# watcher's bodies they replaced did — that stream is the watcher's log — and
|
|
175
|
+
# add one line of their own, naming the lock, when `hr_registry_set` cannot
|
|
176
|
+
# take the registry lock. That silence is why the lane reports a lock it BROKE through a
|
|
169
177
|
# variable instead of a log line — the caller owns the log.
|
|
170
178
|
#
|
|
171
179
|
# NAMING. Every function is prefixed `hr_`; every variable this file touches
|
|
@@ -177,7 +185,9 @@
|
|
|
177
185
|
# `HR_LANE_RANK`, `HR_LANE_STATE`, `HR_LANE_RESUME_AT`, `HR_LANE_OBSERVED_AT`,
|
|
178
186
|
# `HR_LANE_OBSERVED_REPO`, `HR_LANE_OWNER_SLUG`, `HR_LANE_OWNER_PID`,
|
|
179
187
|
# `HR_LANE_OWNER_AT` and `HR_LANE_BROKEN_OWNER`, and the remote state bundle's
|
|
180
|
-
# names, which `hr_remote_names_var` assigns
|
|
188
|
+
# names, which `hr_remote_names_var` assigns, with `HR_REMOTE_PLANNING_PATHS`
|
|
189
|
+
# (`hr_remote_planning_paths`) and `HR_REMOTE_PLANNING_PLACED` /
|
|
190
|
+
# `HR_REMOTE_PLANNING_KEPT` (`hr_remote_bundle_restore`). Every one of them is assigned
|
|
181
191
|
# before it is read by the function that owns it, so an inherited value from a
|
|
182
192
|
# parent process is overwritten rather than believed.
|
|
183
193
|
#
|
|
@@ -1110,31 +1120,187 @@ hr_push_env_files() {
|
|
|
1110
1120
|
# Each function takes the registry file as its first argument — the caller
|
|
1111
1121
|
# resolves it as `hr_state_path <root> autonomous_logs/registry.json` — and is
|
|
1112
1122
|
# write exception 2 in the header. Every value is written as a JSON string;
|
|
1113
|
-
# every write stamps `branch` and `updated_at` on the record
|
|
1114
|
-
#
|
|
1115
|
-
#
|
|
1123
|
+
# every write stamps `branch` and `updated_at` on the record. No shell option is
|
|
1124
|
+
# assumed: the caller may set `-e`, `-u` or neither.
|
|
1125
|
+
#
|
|
1126
|
+
# EVERY WRITE IS A LOCKED READ-MODIFY-WRITE. Every script sharing the file
|
|
1127
|
+
# writes it, and so does the watcher's own `( … ) &` engine subshell, so two
|
|
1128
|
+
# unserialized writers each read the same record and the second `mv` silently
|
|
1129
|
+
# drops the first one's key. The lock is a `mkdir` of `<file>.lock` holding an
|
|
1130
|
+
# `owner` file with a per-call token — the `mktemp` name of that call's temp
|
|
1131
|
+
# file, because `$$` inside a subshell is the parent's pid and bash 3.2 has no
|
|
1132
|
+
# per-subshell pid variable — and only a call whose token is still in `owner`
|
|
1133
|
+
# releases it.
|
|
1134
|
+
#
|
|
1135
|
+
# STALENESS IS THE LOCK DIRECTORY'S AGE, NEVER A PID. The stall watchdog kills
|
|
1136
|
+
# the engine subshell mid-write, so a dead holder is expected, and a pid says
|
|
1137
|
+
# nothing about a lock the killed subshell took under its parent's `$$`. A write
|
|
1138
|
+
# is one `jq` over a file of a few records, well under a second, so a lock 10 s
|
|
1139
|
+
# old is a crashed holder; a waiter polls every 0.05 s for up to 12 s — past the
|
|
1140
|
+
# stale age, so a waiter that arrives just after a crash breaks the lock rather
|
|
1141
|
+
# than giving up. Both are constants here, not environment values: the header
|
|
1142
|
+
# allows policy-carrying environment values for the lane only.
|
|
1143
|
+
#
|
|
1144
|
+
# BREAKERS ARE SERIALIZED, AND NONE RENAMES A LIVE LOCK. A waiter that judges
|
|
1145
|
+
# the lock stale takes a second `mkdir` mutex, `<file>.lock.break`, judges the
|
|
1146
|
+
# lock's age again under it, and only then renames the lock aside and empties
|
|
1147
|
+
# it, as `hr_lane_acquire` does. A lock re-taken since the first judgement is
|
|
1148
|
+
# fresh, so the second leaves it alone. Between the second judgement and the
|
|
1149
|
+
# rename, nothing but a breaker or the lock's own holder frees the path:
|
|
1150
|
+
# breakers are serialized, and the holder of a lock that old is dead by the
|
|
1151
|
+
# stale rule. The watchdog can kill a breaker inside the mutex, so a mutex 10 s
|
|
1152
|
+
# old is removed by the next waiter. That removal is the one window left open.
|
|
1153
|
+
# Two waiters that judge one abandoned mutex old together can both remove it,
|
|
1154
|
+
# and the second can remove the fresh mutex the first has just taken. Two
|
|
1155
|
+
# breakers then judge the lock at once, so reaching it takes a breaker killed
|
|
1156
|
+
# inside the mutex, a stale lock and three concurrent writers.
|
|
1157
|
+
#
|
|
1158
|
+
# A write that cannot take the lock prints one stderr line naming it, writes
|
|
1159
|
+
# nothing and returns 1.
|
|
1160
|
+
#
|
|
1161
|
+
# ONE CALL, SEVERAL KEYS. `hr_registry_set <file> <branch> <key> <value> [<key>
|
|
1162
|
+
# <value> …]` writes every pair in one `jq` pass under one lock and one `mv`.
|
|
1163
|
+
# A caller writing two keys that a concurrent reader must never see apart — a
|
|
1164
|
+
# status and the reason for it — MUST pass them in one call.
|
|
1165
|
+
#
|
|
1166
|
+
# READERS TAKE NO LOCK. The temp file sits beside the registry, so the `mv` is a
|
|
1167
|
+
# same-filesystem rename and `hr_registry_get`, `hr_registry_branches` and any
|
|
1168
|
+
# other `jq` over the file see the old record or the new one, never a partial
|
|
1169
|
+
# one. `hr_registry_init` creates the file by `ln`-ing a complete temp file to
|
|
1170
|
+
# the registry name, which fails when the name exists, so a reader's create
|
|
1171
|
+
# never truncates a registry a writer has just created.
|
|
1116
1172
|
# ---------------------------------------------------------------------------
|
|
1117
1173
|
|
|
1118
|
-
#
|
|
1174
|
+
# The directory holding <file> — where its temp files go, so every `mv` over it
|
|
1175
|
+
# is a same-filesystem rename.
|
|
1176
|
+
hr_registry_dir() {
|
|
1177
|
+
local file="${1-}" dir
|
|
1178
|
+
case "$file" in
|
|
1179
|
+
*/*)
|
|
1180
|
+
dir="${file%/*}"
|
|
1181
|
+
[ -n "$dir" ] || dir=/
|
|
1182
|
+
;;
|
|
1183
|
+
*) dir=. ;;
|
|
1184
|
+
esac
|
|
1185
|
+
printf '%s\n' "$dir"
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
# Create an empty registry at <file> when none exists. Atomic: the name is
|
|
1189
|
+
# either absent or a complete `{"runs":{}}` — never truncated, never partial.
|
|
1119
1190
|
hr_registry_init() {
|
|
1120
|
-
local file="${1-}"
|
|
1191
|
+
local file="${1-}" dir tmp
|
|
1121
1192
|
[ -n "$file" ] || return 1
|
|
1122
|
-
[ -f "$file" ]
|
|
1193
|
+
[ -f "$file" ] && return 0
|
|
1194
|
+
dir=$(hr_registry_dir "$file")
|
|
1195
|
+
tmp=$(mktemp "$dir/.registry.XXXXXX") || return 1
|
|
1196
|
+
if printf '{"runs":{}}\n' >"$tmp"; then
|
|
1197
|
+
ln "$tmp" "$file" 2>/dev/null || :
|
|
1198
|
+
fi
|
|
1199
|
+
rm -f "$tmp"
|
|
1200
|
+
[ -f "$file" ]
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
# hr_registry_lock <file> <token> — take `<file>.lock` for <token>, breaking a
|
|
1204
|
+
# stale one (see the section comment). 0 when held; 1, with one stderr line,
|
|
1205
|
+
# when the wait ceiling passed.
|
|
1206
|
+
hr_registry_lock() {
|
|
1207
|
+
local lock="${1-}.lock" token="${2-}" polls=0 start="" now m stale bm
|
|
1208
|
+
# 12 — the wait ceiling and 10 — the stale age, both in seconds. The wait is
|
|
1209
|
+
# judged by the clock, because each poll's forks cost more than its sleep;
|
|
1210
|
+
# 240 polls is the ceiling only when `date` gives no epoch.
|
|
1211
|
+
while :; do
|
|
1212
|
+
if mkdir "$lock" 2>/dev/null; then
|
|
1213
|
+
if printf '%s\n' "$token" >"$lock/owner" 2>/dev/null; then
|
|
1214
|
+
return 0
|
|
1215
|
+
fi
|
|
1216
|
+
rm -f "$lock/owner" 2>/dev/null || :
|
|
1217
|
+
rmdir "$lock" 2>/dev/null || :
|
|
1218
|
+
break
|
|
1219
|
+
fi
|
|
1220
|
+
m=$(hr_lane_mtime "$lock")
|
|
1221
|
+
now=$(date +%s 2>/dev/null) || now=0
|
|
1222
|
+
case "$now" in '' | *[!0-9]*) now=0 ;; esac
|
|
1223
|
+
[ -n "$start" ] || start="$now"
|
|
1224
|
+
if [ "$now" -gt 0 ]; then
|
|
1225
|
+
[ $((now - start)) -lt 12 ] || break
|
|
1226
|
+
else
|
|
1227
|
+
[ "$polls" -lt 240 ] || break
|
|
1228
|
+
fi
|
|
1229
|
+
if [ "$m" -gt 0 ] && [ "$now" -gt 0 ] && [ $((now - m)) -ge 10 ]; then
|
|
1230
|
+
if mkdir "$lock.break" 2>/dev/null; then
|
|
1231
|
+
# One breaker at a time: judge the age again under the mutex, so a
|
|
1232
|
+
# lock re-taken since the judgement above is fresh and left alone.
|
|
1233
|
+
m=$(hr_lane_mtime "$lock")
|
|
1234
|
+
now=$(date +%s 2>/dev/null) || now=0
|
|
1235
|
+
case "$now" in '' | *[!0-9]*) now=0 ;; esac
|
|
1236
|
+
if [ "$m" -gt 0 ] && [ "$now" -gt 0 ] && [ $((now - m)) -ge 10 ]; then
|
|
1237
|
+
stale="$lock.stale.${token##*.}"
|
|
1238
|
+
if mv "$lock" "$stale" 2>/dev/null; then
|
|
1239
|
+
rm -f "$stale/owner" 2>/dev/null || :
|
|
1240
|
+
rmdir "$stale" 2>/dev/null || :
|
|
1241
|
+
fi
|
|
1242
|
+
fi
|
|
1243
|
+
rmdir "$lock.break" 2>/dev/null || :
|
|
1244
|
+
polls=$((polls + 1))
|
|
1245
|
+
continue
|
|
1246
|
+
fi
|
|
1247
|
+
# Another breaker holds the mutex. One the watchdog killed inside it
|
|
1248
|
+
# left it behind: a mutex 10 s old is removed here.
|
|
1249
|
+
bm=$(hr_lane_mtime "$lock.break")
|
|
1250
|
+
if [ "$bm" -gt 0 ] && [ $((now - bm)) -ge 10 ]; then
|
|
1251
|
+
rmdir "$lock.break" 2>/dev/null || :
|
|
1252
|
+
fi
|
|
1253
|
+
fi
|
|
1254
|
+
sleep 0.05
|
|
1255
|
+
polls=$((polls + 1))
|
|
1256
|
+
done
|
|
1257
|
+
printf 'hr_registry_set: could not take the registry lock %s\n' "$lock" >&2
|
|
1258
|
+
return 1
|
|
1123
1259
|
}
|
|
1124
1260
|
|
|
1125
|
-
#
|
|
1261
|
+
# hr_registry_unlock <file> <token> — release `<file>.lock` only while its
|
|
1262
|
+
# `owner` still holds <token>; a lock broken and re-taken is someone else's.
|
|
1263
|
+
hr_registry_unlock() {
|
|
1264
|
+
local lock="${1-}.lock" token="${2-}" owner=""
|
|
1265
|
+
[ -r "$lock/owner" ] && { IFS= read -r owner <"$lock/owner" || :; } 2>/dev/null
|
|
1266
|
+
[ -n "$token" ] && [ "$owner" = "$token" ] || return 0
|
|
1267
|
+
rm -f "$lock/owner" 2>/dev/null || :
|
|
1268
|
+
rmdir "$lock" 2>/dev/null || :
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
# hr_registry_set <file> <branch> <key> <value> [<key> <value> …]
|
|
1272
|
+
# 1, writing nothing, on no key/value pair or an odd count of them. Each pair
|
|
1273
|
+
# reaches `jq` as `--arg kN` / `--arg vN` — jq 1.5 has no `$ARGS` — and only
|
|
1274
|
+
# those generated names enter the program text, never a value.
|
|
1126
1275
|
hr_registry_set() {
|
|
1127
|
-
local file="${1-}" branch="${2-}"
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1276
|
+
local file="${1-}" branch="${2-}" dir tmp fields="" i=0 status=0
|
|
1277
|
+
local -a args
|
|
1278
|
+
[ -n "$file" ] && [ "$#" -ge 4 ] && [ $(($# % 2)) -eq 0 ] || return 1
|
|
1279
|
+
shift 2
|
|
1280
|
+
args=(--arg b "$branch" --arg now "$(date '+%Y-%m-%dT%H:%M:%S')")
|
|
1281
|
+
while [ "$#" -gt 0 ]; do
|
|
1282
|
+
args+=(--arg "k$i" "$1" --arg "v$i" "$2")
|
|
1283
|
+
fields="$fields(\$k$i): \$v$i, "
|
|
1284
|
+
i=$((i + 1))
|
|
1285
|
+
shift 2
|
|
1286
|
+
done
|
|
1287
|
+
dir=$(hr_registry_dir "$file")
|
|
1288
|
+
tmp=$(mktemp "$dir/.registry.XXXXXX") || return 1
|
|
1289
|
+
if ! hr_registry_lock "$file" "$tmp"; then
|
|
1135
1290
|
rm -f "$tmp"
|
|
1136
1291
|
return 1
|
|
1137
1292
|
fi
|
|
1293
|
+
hr_registry_init "$file" || :
|
|
1294
|
+
if jq "${args[@]}" "
|
|
1295
|
+
.runs[\$b] = ((.runs[\$b] // {}) + {$fields\"branch\": \$b, \"updated_at\": \$now})
|
|
1296
|
+
" "$file" >"$tmp"; then
|
|
1297
|
+
mv "$tmp" "$file" || status=1
|
|
1298
|
+
else
|
|
1299
|
+
status=1
|
|
1300
|
+
fi
|
|
1301
|
+
[ "$status" -eq 0 ] || rm -f "$tmp"
|
|
1302
|
+
hr_registry_unlock "$file" "$tmp"
|
|
1303
|
+
return "$status"
|
|
1138
1304
|
}
|
|
1139
1305
|
|
|
1140
1306
|
# hr_registry_get <file> <branch> <key> -> the value, or nothing
|
|
@@ -1165,18 +1331,43 @@ hr_registry_branches() {
|
|
|
1165
1331
|
# <bundle>/PAUSE_PROGRESS.md when present
|
|
1166
1332
|
# <bundle>/flow_walker_state <state_dir>/.flow_walker_state, WITHOUT its dot
|
|
1167
1333
|
# <bundle>/run.log <state_dir>/autonomous_logs/<branch>.log; never restored
|
|
1334
|
+
# <bundle>/planning/<path> each of these under <state_dir>, when present:
|
|
1335
|
+
# story_plans/<branch>_story_plan.md
|
|
1336
|
+
# task_plans/<branch>
|
|
1337
|
+
# ui_test_plans/<branch>_ui_test_plan.md
|
|
1338
|
+
# ui_test_plans/<branch>
|
|
1339
|
+
# task_plan_reviews/<branch>
|
|
1340
|
+
# business_parity_reviews/<branch>
|
|
1341
|
+
# architecture_reviews/<branch>
|
|
1342
|
+
# ui_test_plan_reviews/<branch>
|
|
1168
1343
|
#
|
|
1169
1344
|
# The walker state loses its dot because `actions/upload-artifact` skips hidden
|
|
1170
|
-
# files by default.
|
|
1171
|
-
# gitignored machine-local state, and the remote-status and
|
|
1172
|
-
# under `autonomous_logs/`, whose ignore rule already
|
|
1345
|
+
# files by default. THE BUNDLE ITSELF IS NEVER COMMITTED. Every file outside
|
|
1346
|
+
# `planning/` is gitignored machine-local state, and the remote-status and
|
|
1347
|
+
# move-aside paths sit under `autonomous_logs/`, whose ignore rule already
|
|
1348
|
+
# covers them. The files under `planning/` are untracked drafts that the flow
|
|
1349
|
+
# commits itself at its P1/P3 convergence; the bundle only carries them.
|
|
1350
|
+
#
|
|
1351
|
+
# THE PLANNING PATHS ARE A MIRROR of the contracts that write and stage them:
|
|
1352
|
+
# `plugin/instructions/task_plan_writing_instructions_autonomous.md` →
|
|
1353
|
+
# `## Override 3` and `## Override 4` staging lists, and
|
|
1354
|
+
# `<scripts_dir>/flows/task_plan_writing.graph.json` → each node's `findingsFolder`. A path
|
|
1355
|
+
# added there is an edit to `hr_remote_planning_paths`. Only planning is
|
|
1356
|
+
# carried because the walker's one graph is `task_plan_writing.graph.json`;
|
|
1357
|
+
# implementation-phase per-unit review folders never span a pause, which waits
|
|
1358
|
+
# for a clean tracked tree. `HR_REMOTE_STATE_SCHEMA` stays `'1'`: `planning/`
|
|
1359
|
+
# is additive, an older reader ignores it, and a newer reader of an older
|
|
1360
|
+
# bundle finds none.
|
|
1173
1361
|
#
|
|
1174
1362
|
# WHO READS EACH FILE. `status.json`: `remote-run.sh sync` / `status` (into the
|
|
1175
1363
|
# local registry), `continue` / `poll` (the decision, `chain`, the reset time)
|
|
1176
1364
|
# and the next job's seed. The clarification directory and `PAUSE_PROGRESS.md`:
|
|
1177
1365
|
# the next job, and the user's local mirror. The walker state: the next job
|
|
1178
|
-
# only. `
|
|
1179
|
-
#
|
|
1366
|
+
# only. `planning/`: the next job only, never a mirror — an untracked draft left
|
|
1367
|
+
# in the mirror would make its later fast-forward to `origin/<branch>` refuse,
|
|
1368
|
+
# because the draft's own convergence commit adds the same path. `run.log`: the
|
|
1369
|
+
# user only — `sync` copies it to the main checkout's logs directory itself,
|
|
1370
|
+
# and no restore places it.
|
|
1180
1371
|
#
|
|
1181
1372
|
# `status.json` — schema `HR_REMOTE_STATE_SCHEMA`; every value a JSON string:
|
|
1182
1373
|
# schema a reader that does not recognise it treats the bundle as absent
|
|
@@ -1210,6 +1401,27 @@ hr_remote_names_var() {
|
|
|
1210
1401
|
HR_REMOTE_LOGS_DIR='autonomous_logs'
|
|
1211
1402
|
HR_REMOTE_STATUS_SOURCE="$HR_REMOTE_LOGS_DIR/remote_status.json"
|
|
1212
1403
|
HR_REMOTE_SUPERSEDED_DIR="$HR_REMOTE_LOGS_DIR/remote_superseded"
|
|
1404
|
+
HR_REMOTE_PLANNING_DIR='planning'
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
# hr_remote_planning_paths <branch>
|
|
1408
|
+
#
|
|
1409
|
+
# Assigns `HR_REMOTE_PLANNING_PATHS`: the eight planning paths of the format
|
|
1410
|
+
# above, relative to <state_dir>, newline-separated, no trailing slash. 1 with
|
|
1411
|
+
# an empty value for an empty <branch>.
|
|
1412
|
+
hr_remote_planning_paths() {
|
|
1413
|
+
local branch="${1-}"
|
|
1414
|
+
HR_REMOTE_PLANNING_PATHS=''
|
|
1415
|
+
[ -n "$branch" ] || return 1
|
|
1416
|
+
HR_REMOTE_PLANNING_PATHS="story_plans/${branch}_story_plan.md
|
|
1417
|
+
task_plans/$branch
|
|
1418
|
+
ui_test_plans/${branch}_ui_test_plan.md
|
|
1419
|
+
ui_test_plans/$branch
|
|
1420
|
+
task_plan_reviews/$branch
|
|
1421
|
+
business_parity_reviews/$branch
|
|
1422
|
+
architecture_reviews/$branch
|
|
1423
|
+
ui_test_plan_reviews/$branch"
|
|
1424
|
+
return 0
|
|
1213
1425
|
}
|
|
1214
1426
|
|
|
1215
1427
|
# hr_remote_status_write <registry_file> <branch> <out_json> <decision> <detail>
|
|
@@ -1313,10 +1525,12 @@ hr_remote_status_get() {
|
|
|
1313
1525
|
# <root>'s configured state directory. `status.json` is the job's own
|
|
1314
1526
|
# `autonomous_logs/remote_status.json` when present; otherwise it is written
|
|
1315
1527
|
# from <branch>'s registry record with decision `stop`, because a job that never
|
|
1316
|
-
# wrote its status never decided to continue.
|
|
1528
|
+
# wrote its status never decided to continue. Each planning path present is
|
|
1529
|
+
# copied under `planning/`, whether or not the branch tracks it: the restore
|
|
1530
|
+
# never overwrites, so a tracked copy is inert. 0 written; 1 a missing argument,
|
|
1317
1531
|
# a non-empty <out_dir> or a failed copy; 2 <root>'s configuration unresolvable.
|
|
1318
1532
|
hr_remote_bundle_write() {
|
|
1319
|
-
local root="${1-}" branch="${2-}" registry="${3-}" out="${4-}" state base clarify
|
|
1533
|
+
local root="${1-}" branch="${2-}" registry="${3-}" out="${4-}" state base clarify rel dst
|
|
1320
1534
|
[ -n "$root" ] && [ -n "$branch" ] && [ -n "$registry" ] && [ -n "$out" ] || return 1
|
|
1321
1535
|
state=$(hr_state_dir "$root") || return 2
|
|
1322
1536
|
hr_remote_names_var
|
|
@@ -1349,15 +1563,36 @@ hr_remote_bundle_write() {
|
|
|
1349
1563
|
if [ -f "$base/$HR_REMOTE_LOGS_DIR/$branch.log" ]; then
|
|
1350
1564
|
cp "$base/$HR_REMOTE_LOGS_DIR/$branch.log" "$out/$HR_REMOTE_LOG_FILE" 2>/dev/null || return 1
|
|
1351
1565
|
fi
|
|
1566
|
+
hr_remote_planning_paths "$branch" || return 1
|
|
1567
|
+
while IFS= read -r rel; do
|
|
1568
|
+
dst="$out/$HR_REMOTE_PLANNING_DIR/$rel"
|
|
1569
|
+
if [ -f "$base/$rel" ]; then
|
|
1570
|
+
mkdir -p "${dst%/*}" 2>/dev/null || return 1
|
|
1571
|
+
cp "$base/$rel" "$dst" 2>/dev/null || return 1
|
|
1572
|
+
elif [ -d "$base/$rel" ]; then
|
|
1573
|
+
mkdir -p "${dst%/*}" 2>/dev/null || return 1
|
|
1574
|
+
cp -R "$base/$rel" "$dst" 2>/dev/null || return 1
|
|
1575
|
+
fi
|
|
1576
|
+
done <<EOF
|
|
1577
|
+
$HR_REMOTE_PLANNING_PATHS
|
|
1578
|
+
EOF
|
|
1352
1579
|
return 0
|
|
1353
1580
|
}
|
|
1354
1581
|
|
|
1355
1582
|
# hr_remote_bundle_restore <bundle_dir> <root> <branch> <mode>
|
|
1356
1583
|
#
|
|
1357
1584
|
# <mode> `job` places the clarification directory, `PAUSE_PROGRESS.md`, the
|
|
1358
|
-
# walker state (back under its dotted name)
|
|
1359
|
-
# `autonomous_logs/remote_status.json`); `mirror` places the
|
|
1360
|
-
# run log is placed by neither.
|
|
1585
|
+
# walker state (back under its dotted name), the planning drafts and
|
|
1586
|
+
# `status.json` (as `autonomous_logs/remote_status.json`); `mirror` places the
|
|
1587
|
+
# first two only. The run log is placed by neither.
|
|
1588
|
+
#
|
|
1589
|
+
# A PLANNING DRAFT NEVER OVERWRITES. A regular file under `planning/` whose
|
|
1590
|
+
# path lies in `HR_REMOTE_PLANNING_PATHS` and has no `..` segment is placed only
|
|
1591
|
+
# where nothing exists, counted in `HR_REMOTE_PLANNING_PLACED`; one whose target
|
|
1592
|
+
# exists is left byte-identical — the checkout's copy is the branch's committed
|
|
1593
|
+
# record — and counted in `HR_REMOTE_PLANNING_KEPT`. A symlink, a non-regular
|
|
1594
|
+
# entry or a path outside the set counts in neither. Both are `0` at entry and
|
|
1595
|
+
# stay `0` in `mirror` mode.
|
|
1361
1596
|
#
|
|
1362
1597
|
# THE CLARIFICATION DIRECTORY IS REPLACED WHOLESALE, AND NOTHING IS DELETED. An
|
|
1363
1598
|
# existing target is moved aside with one `mv` into
|
|
@@ -1373,7 +1608,9 @@ hr_remote_bundle_write() {
|
|
|
1373
1608
|
# <branch>) or <root>'s configuration is unresolvable.
|
|
1374
1609
|
hr_remote_bundle_restore() {
|
|
1375
1610
|
local bundle="${1-}" root="${2-}" branch="${3-}" mode="${4-}"
|
|
1376
|
-
local state base named target epoch aside n tmp
|
|
1611
|
+
local state base named target epoch aside n tmp pdir file rel p inset
|
|
1612
|
+
HR_REMOTE_PLANNING_PLACED=0
|
|
1613
|
+
HR_REMOTE_PLANNING_KEPT=0
|
|
1377
1614
|
[ -n "$bundle" ] && [ -n "$root" ] && [ -n "$branch" ] || return 1
|
|
1378
1615
|
case "$mode" in
|
|
1379
1616
|
job|mirror) ;;
|
|
@@ -1413,6 +1650,37 @@ hr_remote_bundle_restore() {
|
|
|
1413
1650
|
mkdir -p "$base" 2>/dev/null || return 1
|
|
1414
1651
|
cp "$bundle/$HR_REMOTE_WALKER_FILE" "$base/$HR_REMOTE_WALKER_SOURCE" 2>/dev/null || return 1
|
|
1415
1652
|
fi
|
|
1653
|
+
pdir="$bundle/$HR_REMOTE_PLANNING_DIR"
|
|
1654
|
+
if [ -d "$pdir" ] && [ ! -L "$pdir" ]; then
|
|
1655
|
+
hr_remote_planning_paths "$branch" || return 1
|
|
1656
|
+
# Process substitution, not a pipe: the loop must run in this shell so the
|
|
1657
|
+
# counters and `return 1` reach the caller.
|
|
1658
|
+
while IFS= read -r file; do
|
|
1659
|
+
# Re-tested per line: a name holding a newline arrives split and fails here.
|
|
1660
|
+
[ -f "$file" ] && [ ! -L "$file" ] || continue
|
|
1661
|
+
rel=${file#"$pdir"/}
|
|
1662
|
+
case "/$rel/" in
|
|
1663
|
+
*/../*) continue ;;
|
|
1664
|
+
esac
|
|
1665
|
+
inset=0
|
|
1666
|
+
while IFS= read -r p; do
|
|
1667
|
+
case "$rel" in
|
|
1668
|
+
"$p"|"$p"/*) inset=1; break ;;
|
|
1669
|
+
esac
|
|
1670
|
+
done <<EOF
|
|
1671
|
+
$HR_REMOTE_PLANNING_PATHS
|
|
1672
|
+
EOF
|
|
1673
|
+
[ "$inset" = 1 ] || continue
|
|
1674
|
+
if [ -e "$base/$rel" ] || [ -L "$base/$rel" ]; then
|
|
1675
|
+
HR_REMOTE_PLANNING_KEPT=$((HR_REMOTE_PLANNING_KEPT + 1))
|
|
1676
|
+
continue
|
|
1677
|
+
fi
|
|
1678
|
+
target="$base/$rel"
|
|
1679
|
+
mkdir -p "${target%/*}" 2>/dev/null || return 1
|
|
1680
|
+
cp "$file" "$target" 2>/dev/null || return 1
|
|
1681
|
+
HR_REMOTE_PLANNING_PLACED=$((HR_REMOTE_PLANNING_PLACED + 1))
|
|
1682
|
+
done < <(find "$pdir" -type f 2>/dev/null)
|
|
1683
|
+
fi
|
|
1416
1684
|
mkdir -p "$base/$HR_REMOTE_LOGS_DIR" 2>/dev/null || return 1
|
|
1417
1685
|
tmp=$(mktemp "$base/$HR_REMOTE_STATUS_SOURCE.tmp.XXXXXX" 2>/dev/null) || return 1
|
|
1418
1686
|
if cp "$bundle/$HR_REMOTE_STATUS_FILE" "$tmp" 2>/dev/null \
|
|
@@ -71,11 +71,15 @@
|
|
|
71
71
|
# past a run with none, and stopping at one whose artifact has expired, since
|
|
72
72
|
# an older copy would be staler state. An expired one restores nothing: under
|
|
73
73
|
# --resume answer it exits 2; otherwise it prints a `::warning::` line naming
|
|
74
|
-
# the run, the expiry and the lost counts
|
|
75
|
-
# job continues from the committed ledger. An unexpired one it downloads to `<state_dir>/autonomous_logs/remote_download/<branch>/<id>/`
|
|
74
|
+
# the run, the expiry and the lost counts, clarification history and
|
|
75
|
+
# uncommitted planning drafts, and the job continues from the committed ledger. An unexpired one it downloads to `<state_dir>/autonomous_logs/remote_download/<branch>/<id>/`
|
|
76
76
|
# (skipped when that directory already holds its status.json); and restores it
|
|
77
77
|
# in `job` mode — on every --resume kind, `none` included, because a reused
|
|
78
|
-
# branch keeps its clarification history.
|
|
78
|
+
# branch keeps its clarification history. A job-mode restore also places the
|
|
79
|
+
# bundle's `planning/` drafts at their paths under the state directory, never
|
|
80
|
+
# over a file the checkout already has, and when it placed or kept any prints
|
|
81
|
+
# `placed <n> planning file(s) for <branch>; kept <n> the checkout already
|
|
82
|
+
# carries`. Then, under --resume answer, it
|
|
79
83
|
# writes each `"<n>": "<text>"` entry to `clarifications/<branch>/answer_<n>.md`
|
|
80
84
|
# with the exact bytes, after checking every entry first; and with
|
|
81
85
|
# `HARNESS_INPUT_PARK_LOOP_CLEAR` exactly `true` it sets `park_loop_cycles` to
|
|
@@ -230,7 +234,8 @@
|
|
|
230
234
|
# `mirror` mode into the record's `worktree`, `run.log` copied to the main
|
|
231
235
|
# checkout's `autonomous_logs/<branch>.remote.log`, and `status`,
|
|
232
236
|
# `pause_reason`, `usage_resume_at`, `park_loop_cycles`, `remote_run_id`,
|
|
233
|
-
# `remote_run_url`, `remote_detail` and `remote_synced_at` written
|
|
237
|
+
# `remote_run_url`, `remote_detail` and `remote_synced_at` written. A
|
|
238
|
+
# `mirror` restore places no planning draft
|
|
234
239
|
# 4. no artifact, while some bundle exists (`remote_run_id` is set, or an
|
|
235
240
|
# older finished run carries one): a job that died before its upload.
|
|
236
241
|
# `paused` / `killed`, `remote_run_id` / `remote_run_url` re-pointed at
|
|
@@ -276,7 +281,7 @@
|
|
|
276
281
|
# no job to stop and may complete before its cancel lands — trying every one even
|
|
277
282
|
# after a failure. (3) Only when (1) and (2) all succeeded, and only when a
|
|
278
283
|
# local registry record exists, it writes `remote_stopped_at` and sets `status`
|
|
279
|
-
# to `failed`
|
|
284
|
+
# to `failed` in one `hr_registry_set` call; a partial stop leaves the record alone
|
|
280
285
|
# and exits 3, so running `stop` again is the remedy.
|
|
281
286
|
#
|
|
282
287
|
# `warm` dispatches action=warm on GitHub's OWN default branch (`gh repo view
|
|
@@ -294,7 +299,8 @@
|
|
|
294
299
|
# `<state_dir>/autonomous_logs/remote_download/<branch>/<id>/` and
|
|
295
300
|
# `<branch>.remote.log` in the main checkout, plus the mirror restore
|
|
296
301
|
# `hr_remote_bundle_restore` performs in the record's `worktree`; for
|
|
297
|
-
# `restore`, that download directory, the job restore
|
|
302
|
+
# `restore`, that download directory, the job restore (the planning drafts
|
|
303
|
+
# among it), `answer_<n>.md` and the
|
|
298
304
|
# `park_loop_cycles` rewrite of `remote_status.json`, all in the job's
|
|
299
305
|
# checkout; for `save`, <out_dir> and the step summary; for `poll`, its
|
|
300
306
|
# download directories and `<state_dir>/autonomous_logs/poll_state/previous/`
|
|
@@ -363,7 +369,8 @@
|
|
|
363
369
|
# flow_walker_state) and GITHUB_RUN_ID set to another id:
|
|
364
370
|
# restore bash scripts/remote-run.sh restore feat_x --resume none -> 0; the
|
|
365
371
|
# checkout carries clarifications/feat_x/question_1.md,
|
|
366
|
-
# .flow_walker_state and autonomous_logs/remote_status.json
|
|
372
|
+
# .flow_walker_state and autonomous_logs/remote_status.json, and
|
|
373
|
+
# places the bundle's planning/ drafts where the checkout has none
|
|
367
374
|
# answer HARNESS_INPUT_ANSWERS='{"1":"Use B.\n"}' ... --resume answer -> 0;
|
|
368
375
|
# clarifications/feat_x/answer_1.md holds exactly `Use B.` + newline
|
|
369
376
|
# no question HARNESS_INPUT_ANSWERS='{"2":"x"}' ... --resume answer
|
|
@@ -381,7 +388,7 @@
|
|
|
381
388
|
# -> prints the expired line, "$r" byte-identical
|
|
382
389
|
# save bash scripts/remote-run.sh save feat_x /tmp/b -> 0; /tmp/b holds
|
|
383
390
|
# status.json, clarifications/feat_x/, flow_walker_state (and
|
|
384
|
-
# PAUSE_PROGRESS.md, run.log when present); with
|
|
391
|
+
# PAUSE_PROGRESS.md, run.log, planning/ when present); with
|
|
385
392
|
# GITHUB_STEP_SUMMARY=/tmp/s, /tmp/s gains the status table
|
|
386
393
|
# never started no remote_status.json and no registry: save -> 0, /tmp/b
|
|
387
394
|
# empty
|
|
@@ -811,8 +818,7 @@ verb_stop() {
|
|
|
811
818
|
|
|
812
819
|
registry=$(hr_state_path "$root" autonomous_logs/registry.json) || registry=""
|
|
813
820
|
if [ -n "$registry" ] && [ -f "$registry" ] && [ -n "$(hr_registry_get "$registry" "$branch" branch)" ]; then
|
|
814
|
-
hr_registry_set "$registry" "$branch" remote_stopped_at "$stopped_at" \
|
|
815
|
-
&& hr_registry_set "$registry" "$branch" status failed \
|
|
821
|
+
hr_registry_set "$registry" "$branch" remote_stopped_at "$stopped_at" status failed \
|
|
816
822
|
|| echo "remote-run.sh: stopped on GitHub, but the local record of $branch could not be updated" >&2
|
|
817
823
|
fi
|
|
818
824
|
echo "remote-run.sh: stopped $branch"
|
|
@@ -904,6 +910,17 @@ set_or_fail() {
|
|
|
904
910
|
}
|
|
905
911
|
}
|
|
906
912
|
|
|
913
|
+
# set_many_or_fail <key> <value> [<key> <value> …] — every pair in one write, so
|
|
914
|
+
# a concurrent reader never sees an outcome half-applied.
|
|
915
|
+
set_many_or_fail() {
|
|
916
|
+
local keys="" i
|
|
917
|
+
hr_registry_set "$registry" "$branch" "$@" || {
|
|
918
|
+
for ((i = 1; i <= $#; i += 2)); do keys="$keys${keys:+, }${!i}"; done
|
|
919
|
+
echo "remote-run.sh: writing $keys of $branch to '$registry' failed" >&2
|
|
920
|
+
exit "$EXIT_USAGE"
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
907
924
|
verb_status() {
|
|
908
925
|
local runs finished_id synced_id field
|
|
909
926
|
list_runs
|
|
@@ -935,18 +952,14 @@ verb_status() {
|
|
|
935
952
|
sync_expired() {
|
|
936
953
|
local line
|
|
937
954
|
line=$(expired_line "$1")
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
set_or_fail remote_run_id "$1"
|
|
941
|
-
set_or_fail remote_run_url "$2"
|
|
942
|
-
set_or_fail remote_detail "$line"
|
|
943
|
-
set_or_fail remote_synced_at "$3"
|
|
955
|
+
set_many_or_fail status paused pause_reason expired remote_run_id "$1" \
|
|
956
|
+
remote_run_url "$2" remote_detail "$line" remote_synced_at "$3"
|
|
944
957
|
echo "remote-run.sh: $line"
|
|
945
958
|
}
|
|
946
959
|
|
|
947
960
|
verb_sync() {
|
|
948
961
|
local worktree runs newest id state url synced_id now older download status_file
|
|
949
|
-
local status reason detail
|
|
962
|
+
local status reason detail resume_at cycles
|
|
950
963
|
worktree=$(hr_registry_get "$registry" "$branch" worktree)
|
|
951
964
|
if [ -z "$worktree" ] || [ ! -d "$worktree" ]; then
|
|
952
965
|
echo "remote-run.sh: refused, nothing written: the mirror working copy '$worktree' of $branch is missing" >&2
|
|
@@ -965,8 +978,7 @@ verb_sync() {
|
|
|
965
978
|
now=$(date +%s)
|
|
966
979
|
|
|
967
980
|
if [ "$state" != completed ]; then
|
|
968
|
-
|
|
969
|
-
set_or_fail remote_synced_at "$now"
|
|
981
|
+
set_many_or_fail status running remote_synced_at "$now"
|
|
970
982
|
echo "remote-run.sh: run $id of $branch is $state; the record is running, nothing downloaded"
|
|
971
983
|
return 0
|
|
972
984
|
fi
|
|
@@ -1039,14 +1051,11 @@ verb_sync() {
|
|
|
1039
1051
|
detail="the job ended mid-run (its bundle still says running): $url"
|
|
1040
1052
|
fi
|
|
1041
1053
|
[ -n "$detail" ] || detail="synced from $url"
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
set_or_fail remote_run_url "$url"
|
|
1048
|
-
set_or_fail remote_detail "$detail"
|
|
1049
|
-
set_or_fail remote_synced_at "$now"
|
|
1054
|
+
resume_at=$(hr_remote_status_get "$status_file" usage_resume_at) || resume_at=""
|
|
1055
|
+
cycles=$(hr_remote_status_get "$status_file" park_loop_cycles) || cycles=""
|
|
1056
|
+
set_many_or_fail status "$status" pause_reason "$reason" usage_resume_at "$resume_at" \
|
|
1057
|
+
park_loop_cycles "$cycles" remote_run_id "$id" remote_run_url "$url" \
|
|
1058
|
+
remote_detail "$detail" remote_synced_at "$now"
|
|
1050
1059
|
echo "remote-run.sh: synced run $id of $branch: $status${reason:+ ($reason)}"
|
|
1051
1060
|
return 0
|
|
1052
1061
|
fi
|
|
@@ -1061,20 +1070,17 @@ verb_sync() {
|
|
|
1061
1070
|
done
|
|
1062
1071
|
fi
|
|
1063
1072
|
if [ "$bundle_exists" -eq 1 ]; then
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
set_or_fail remote_run_url "$url"
|
|
1068
|
-
set_or_fail remote_detail "run $id ended with no state bundle (killed, cancelled or replaced): $url"
|
|
1069
|
-
set_or_fail remote_synced_at "$now"
|
|
1073
|
+
set_many_or_fail status paused pause_reason killed remote_run_id "$id" remote_run_url "$url" \
|
|
1074
|
+
remote_detail "run $id ended with no state bundle (killed, cancelled or replaced): $url" \
|
|
1075
|
+
remote_synced_at "$now"
|
|
1070
1076
|
echo "remote-run.sh: run $id of $branch left no bundle; the record is paused (killed), nothing restored"
|
|
1071
1077
|
return 0
|
|
1072
1078
|
fi
|
|
1073
1079
|
|
|
1074
1080
|
# Case 5 — no bundle in any run.
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1081
|
+
set_many_or_fail status failed \
|
|
1082
|
+
remote_detail "no run of $branch ever uploaded a state bundle; newest: $url" \
|
|
1083
|
+
remote_synced_at "$now"
|
|
1078
1084
|
echo "remote-run.sh: no run of $branch carries a state bundle; the record is failed"
|
|
1079
1085
|
}
|
|
1080
1086
|
|
|
@@ -1141,7 +1147,7 @@ verb_restore() {
|
|
|
1141
1147
|
if [ "$PREV_RUN_STATE" = expired ]; then
|
|
1142
1148
|
[ "$resume" != answer ] \
|
|
1143
1149
|
|| restore_refuse "the state bundle of run $id expired on $BUNDLE_EXPIRES_AT, so its questions can no longer be answered here: resume from the committed ledger with $RESUME_HINT $branch, or re-drop the task; nothing written"
|
|
1144
|
-
echo "::warning::remote-run.sh: the state bundle of run $id expired on $BUNDLE_EXPIRES_AT: the park-loop, auto-resume and stall counts
|
|
1150
|
+
echo "::warning::remote-run.sh: the state bundle of run $id expired on $BUNDLE_EXPIRES_AT: the park-loop, auto-resume and stall counts, the clarification history and any planning drafts not yet committed that it carried are lost; this job continues from the committed ledger"
|
|
1145
1151
|
elif [ -z "$id" ]; then
|
|
1146
1152
|
[ "$resume" != answer ] \
|
|
1147
1153
|
|| restore_refuse "--resume answer, but no finished run of $branch carries a state bundle; nothing written"
|
|
@@ -1164,7 +1170,11 @@ verb_restore() {
|
|
|
1164
1170
|
fi
|
|
1165
1171
|
hr_remote_bundle_restore "$download" "$root" "$branch" job
|
|
1166
1172
|
case $? in
|
|
1167
|
-
0)
|
|
1173
|
+
0)
|
|
1174
|
+
echo "remote-run.sh: restored the bundle of run $id into $root"
|
|
1175
|
+
[ $((HR_REMOTE_PLANNING_PLACED + HR_REMOTE_PLANNING_KEPT)) -eq 0 ] \
|
|
1176
|
+
|| echo "remote-run.sh: placed $HR_REMOTE_PLANNING_PLACED planning file(s) for $branch; kept $HR_REMOTE_PLANNING_KEPT the checkout already carries"
|
|
1177
|
+
;;
|
|
1168
1178
|
2) restore_refuse "the bundle in '$download' is unrecognised for $branch; nothing restored" ;;
|
|
1169
1179
|
*) restore_fail "restoring '$download' into '$root' failed" ;;
|
|
1170
1180
|
esac
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# autonomous_logs/
|
|
2
2
|
|
|
3
|
-
One readable transcript `<branch>.log` and one raw event log `<branch>.stream.jsonl` per dispatched run, plus the daemon's own `watcher.log`, the service manager's capture of its standard output and error as `watcher.out.log` and `watcher.err.log`, and `registry.json` — the run registry that indexes every run's status. All of it resolves to `<state_dir>/autonomous_logs/` **in the main checkout**, whichever sibling working copy a run actually executes in, so one repository has one set of these files however many runs it has in flight.
|
|
3
|
+
One readable transcript `<branch>.log` and one raw event log `<branch>.stream.jsonl` per dispatched run, plus the daemon's own `watcher.log`, the service manager's capture of its standard output and error as `watcher.out.log` and `watcher.err.log`, and `registry.json` — the run registry that indexes every run's status. All of it resolves to `<state_dir>/autonomous_logs/` **in the main checkout**, whichever sibling working copy a run actually executes in, so one repository has one set of these files however many runs it has in flight. A `registry.json.lock` directory beside the registry exists only for the width of one registry write; one left over after a crash is broken automatically by the next writer, and is never removed by hand while a watcher runs.
|
|
4
4
|
|
|
5
5
|
Everything here is written by the run daemon and read by the operator watching or diagnosing a run: the transcript is formatted live and tailable while the run is going, and the raw stream beside it is the deep-debug copy and the one thing the usage gate can parse, since a run cannot read the stream it is itself producing. The registry has readers beyond the operator, and that is what makes it different in kind from its neighbours: it is the single record of which runs exist and what state each is in, and the harness's status command, its pause and resume commands, `<scripts_dir>/restart-watcher.sh` and `<scripts_dir>/cleanup-merged-worktrees.sh` all read it before they act, and `<scripts_dir>/remote-run.sh` reads and writes it for a run executing on GitHub Actions.
|
|
6
6
|
|