#!/usr/bin/env bash # restruct_cycle.sh — the Phase-37 batch cycle, mechanised (T3; cloned from tools/delever_cycle.sh). For each batch k in [START..END]: # 1. the tree must be clean (src/ + config/); the oracle's calibration is refreshed (it is keyed to HEAD: every commit stales it) and the # snapshot baseline is refreshed after a green fleet run (the GUARD refuses an object that does not reproduce, R56/R112); # 2. `restruct.py --apply --rung R --batch N --label k` judges every unit of the next N files and leaves the winning texts in place; # the tool's exit code and its `final X/X identical` are read (R97/R66); # 3. the outer gate: the CLEAN fleet run (make clean && extract-all && check-all -> `218 passed, 0 failed of 218`, R22); # 4. the type census is rerun (the readability series' snapshot reads it), the phase log gets the batch's entry, the 🛑 headline is # refreshed (R101), and src/ + the ledger/batch record/log + the census + the phase log are committed (R42). # Stops on the first red (a non-zero tool exit, a final N/M with N != M, a fleet run that is not 218/218) — the batch's files are then # still in place: inspect, then `tools/restruct.py --restore` (never git checkout, R102). Stops cleanly when nothing is drawable. # Usage: [LABEL_PREFIX=t4_] [TASK=T4] [REDRAW="DECL-KEPT"] [CENSUS=0] tools/restruct_cycle.sh START END [BATCH=50] [RUNG=D|S|L] [ONLY="alias1 alias2 …"] [HEADERS=1] # RUN IT DETACHED from the Claude Code harness (R115 — its low-memory guard kills a long BACKGROUND task; a SIGKILL skips a restore): # setsid nohup bash -c 'TASK=T4 tools/restruct_cycle.sh 1 8 50 D; echo "cycle exit=$?"' > .run/P37/restruct/cycle_.log 2>&1 & # and watch the log (`until grep -q 'cycle exit=' ; do sleep 60; done`). A killed batch: `tools/restruct.py --restore`. # size a call so one batch stays < 10 min: a D unit ≈ 0.15 s per compile (~n+2 compiles per TU on a DIFFERS ladder); an S body ≈ 2 s; # a header ≈ 35 s per judgement (every includer) — headers in small batches. set -o pipefail cd "$(dirname "$0")/.." || exit 2 START=$1; END=$2; BATCH=${3:-50}; RUNG=${4:-D}; ONLY=${5:-} [ -n "$START" ] && [ -n "$END" ] || { echo "usage: $0 START END [BATCH] [D|S|L] [ONLY]"; exit 2; } case "$RUNG" in D|S|L) ;; *) echo "RUNG must be D, S or L"; exit 2;; esac ONLYFLAG=""; [ -n "$ONLY" ] && ONLYFLAG="--only $ONLY" REDRAWFLAG=""; [ -n "${REDRAW:-}" ] && REDRAWFLAG="--redraw $REDRAW" HFLAG=""; [ -n "${HEADERS:-}" ] && HFLAG="--headers" FLEET=$(ls config/check.*.sha | wc -l) PY=.venv/bin/python MARKER='## 🛑 SESSION CHECKPOINT' mkdir -p .run/P37/restruct .run/P37/baseline for k in $(seq "$START" "$END"); do label="${LABEL_PREFIX:-}${RUNG}${k}" log=.run/P37/restruct/run_${label}.log r22=.run/P37/baseline/r22_${label}.log [ -z "$(git status --short -- src config)" ] || { echo "cycle: src/ or config/ is dirty — commit, or tools/restruct.py --restore (R42/R102)"; exit 3; } [ -z "$(git status --short --porcelain src | grep '^??')" ] || { echo "cycle: untracked files under src/ — sweep them first"; exit 3; } $PY tools/delever_oracle.py --status >/dev/null 2>&1 || { $PY tools/delever_oracle.py --snapshot-baseline > .run/P37/restruct/snapshot_${label}.log 2>&1 || { echo "cycle: the snapshot refresh FAILED/REFUSED"; tail -4 .run/P37/restruct/snapshot_${label}.log; exit 1; } grep -q 'REFUSED' .run/P37/restruct/snapshot_${label}.log && { echo "cycle: the snapshot refresh was REFUSED (build/ is not the tree's — run the clean fleet gate first)"; exit 1; } $PY tools/delever_oracle.py --calibrate ov_SC04_011 ov_SC03_015 md_SC07_004 main -j 16 > .run/P37/restruct/calibrate_${label}.log 2>&1 \ || { echo "cycle: calibration FAILED"; tail -5 .run/P37/restruct/calibrate_${label}.log; exit 1; } } { /usr/bin/time -f "$label wall=%e s" $PY tools/restruct.py --apply --rung "$RUNG" --batch "$BATCH" --label "$label" $HFLAG $ONLYFLAG $REDRAWFLAG -j 12; echo "exit=$?"; } > "$log" 2>&1 grep -q '^exit=0$' "$log" || { echo "cycle: batch $label — restruct exited non-zero"; tail -4 "$log"; exit 1; } if grep -qE 'nothing to do' "$log"; then echo "cycle: nothing drawable at $label — done"; exit 0; fi vline=$(grep -oE "restruct: batch .*" "$log" | tail -1) [ -n "$vline" ] || { echo "cycle: batch $label — no verify line in $log (R32: an empty line is not a pass)"; tail -3 "$log"; exit 1; } final=$(echo "$vline" | grep -oE 'final [0-9]+/[0-9]+' | grep -oE '[0-9]+/[0-9]+') [ -n "$final" ] && [ "${final%/*}" = "${final#*/}" ] || { echo "cycle: batch $label — $vline"; exit 1; } if [ "$(echo "$vline" | grep -oE 'written [0-9]+' | grep -oE '[0-9]+')" = "0" ]; then echo "cycle: batch $label wrote no file — $vline"; fi { /usr/bin/time -f "wall=%e s user=%U s sys=%S s" bash -c 'set -o pipefail; make clean && make extract-all JOBS=16 && make check-all JOBS=16'; echo "exit=$?"; } > "$r22" 2>&1 line=$(grep -oE "check-all: [0-9]+ passed, [0-9]+ failed of [0-9]+" "$r22") if ! grep -q '^exit=0$' "$r22" || [ "$line" != "check-all: $FLEET passed, 0 failed of $FLEET" ]; then echo "cycle: R22 RED after batch $label — $line (the batch's files are in place: inspect, then tools/restruct.py --restore)"; exit 1; fi cline="(census skipped)" if [ "${CENSUS:-1}" != "0" ]; then $PY tools/type_census.py -j 16 --sites > .run/P37/census/census_${label}.log 2>&1 || { echo "cycle: the type census FAILED after batch $label"; tail -3 .run/P37/census/census_${label}.log; exit 1; } cline=$(grep -oE "^type_census: .*" .run/P37/census/type_census.txt | tail -1) [ -n "$cline" ] || { echo "cycle: the census table carries no type_census line after batch $label"; exit 1; } $PY tools/readability_progress.py --snapshot "$label" > /dev/null 2>&1 || echo "cycle: readability snapshot refused (a stale census?) — continuing" fi TASK=${TASK:-T4} $PY - "$label" "$vline" "$line" "$cline" "$(grep -oE 'wall=[0-9.]+ s' "$r22" | head -1)" "$MARKER" <<'PYEOF' || exit 1 import os, pathlib, re, sys, time, subprocess label, vline, r22line, cline, wall, marker = sys.argv[1:7] task = os.environ.get("TASK", "T4") p = pathlib.Path("phase-ends/CURRENT_PHASE.md"); t = p.read_text() head = subprocess.run(["git", "rev-parse", "--short", "HEAD"], capture_output=True, text=True).stdout.strip() entry = (f"\n- **{time.strftime('%Y-%m-%d')} — {task} batch `{label}`** (`.run/P37/restruct/run_{label}.log`, `batch_{label}.json`): `{vline}` → R22 " f"(`.run/P37/baseline/r22_{label}.log`) **`{r22line}`** (`{wall}`) → census `{cline}`.") assert ("\n" + marker) in t, "phase log marker (the checkpoint heading) not found" t = t.replace("\n" + marker, entry + "\n\n" + marker, 1) lines = t.split("\n") for i, l in enumerate(lines): if l.startswith("## 🛑 SESSION CHECKPOINT"): base = re.sub(r"\s*\|\s*last batch.*$", "", l) lines[i] = f"{base} | last batch `{label}` on {head}: {vline.split(' — ', 1)[-1][:160]}" break p.write_text("\n".join(lines)) print("log:", label) PYEOF for i in 1 2 3 4 5 6; do [ -e .git/index.lock ] || break if [ -z "$(ps -eo cmd | grep -E '^git |/git ' | grep -v grep)" ]; then echo "cycle: stale .git/index.lock (no git process) — removed"; rm -f .git/index.lock; break; fi sleep 10 done git add -A src && git add .run/P37/restruct .run/P37/census .run/P37/baseline phase-ends/CURRENT_PHASE.md docs/readability.md docs/readability-progress.tsv 2>/dev/null; git add .run/P36/delever/calibration.json 2>/dev/null git commit -q -m "src(phase-37): ${TASK:-T4} batch $label — $vline | R22 $line | $cline (restruct_cycle)" || exit 1 echo "cycle: batch $label committed $(git log --oneline -1 | cut -c1-9) — $vline | $line" done echo "cycle: batches $START..$END done"