Something went wrong. Try again.
Scaffolds custom tools for a devenv environment
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494#!/usr/bin/env bash# Tests for delegate, against a throwaway jj repo, with opencode stubbed and# the real `workspace` and bwrap. Run from devkit: bash delegate/testset -euo pipefail
src="$(cd "$(dirname "$0")/.." && pwd)"T="$(mktemp -d "${TMPDIR:-/tmp}/delegate-test.XXXXXX")"trap 'rm -rf "$T"' EXITpass=0 fail=0ok() { pass=$((pass + 1)); echo "ok $*"; }bad() { fail=$((fail + 1)); echo "FAIL $*"; }# expect CODE DESC -- CMD...: CMD exits with CODE.expect() { local want=$1 desc=$2 rc=0; shift 3 "$@" >"$T/out" 2>"$T/err" || rc=$? if [ "$rc" = "$want" ]; then ok "$desc"; else bad "$desc (exit $rc, want $want)"; sed 's/^/ /' "$T/err" | tail -n 15; fi}has() { if grep -q -- "$2" <<<"$3"; then ok "$1"; else bad "$1: no '$2' in:"; sed 's/^/ /' <<<"$3"; fi; }hasnt() { if grep -q -- "$2" <<<"$3"; then bad "$1: '$2' in:"; sed 's/^/ /' <<<"$3"; else ok "$1"; fi; }yes_() { if eval "$2"; then ok "$1"; else bad "$1 ($2)"; fi; }J() { jj --no-pager --ignore-working-copy "$@"; }desc() { J log --no-graph -r "$1" -T description; }cid() { J log --no-graph -r "$1" -T commit_id; }# land_one OLD DESC: trunk got exactly one change (one task == one commit).land_one() { [ "$(cid main-)" = "$1" ] && ok "$2: one change on main" || bad "$2: main- isn't the old main"; }
# The stub: runs $FAKE_SCRIPT (from a file named by $FAKE) in the task's# directory, then prints a report event that also says whether the prompt# had a fix section.mkdir -p "$T/bin" "$T/proj" "$T/state"ln -s "$src/workspace/workspace" "$T/bin/workspace"ln -s "$src/delegate/delegate" "$T/bin/delegate"cat >"$T/bin/fake-opencode" <<'EOF'#!/usr/bin/env bashcase $1 in session) # export SID: answer with $FAKE_USED, else the model run was given. m="$(cat "$FAKE_STATE/model" 2>/dev/null)"; m=${FAKE_USED:-$m} printf '{"messages":[{"type":"assistant","model":{"providerID":"%s","id":"%s"}}]}\n' "${m%%/*}" "${m#*/}" exit 0 ;; run) ;; *) exit 2 ;;esacargs=("$@")for i in "${!args[@]}"; do [ "${args[$i]}" = --model ] && printf '%s' "${args[$((i + 1))]}" >"$FAKE_STATE/model"; doneprompt="$(cat)"printf '%s\n' "$prompt" >"$FAKE_STATE/prompt"fixed=nogrep -q '^## Fix this round' <<<"$prompt" && fixed=yes[ -z "${FAKE_EARLY:-}" ] || printf '{"type":"text","sessionID":"ses_fake","part":{"text":"EARLY %s"}}\n' "$FAKE_EARLY"[ -z "${FAKE:-}" ] || bash "$FAKE" >&2# Many events, each with the session ID (a long session).[ -z "${FAKE_SPAM:-}" ] || yes '{"type":"step_start","sessionID":"ses_fake","part":{}}' | head -n "$FAKE_SPAM"printf '{"type":"text","sessionID":"ses_fake","part":{"text":"REPORT fix=%s args=%s"}}\n' "$fixed" "$*"EOFchmod +x "$T/bin/fake-opencode"# devenv, only for re-running delegate in a "fresh" shell (DELEGATE_DEVENV=0# keeps it out of tasks).cat >"$T/bin/devenv" <<'EOF'#!/usr/bin/env bash[ "$1" = shell ] && [ "$2" = -- ] || exit 2shift 2echo ran >>"$FAKE_STATE/devenv"if grep -qs broken broken.nix; then for i in $(seq 80); do printf '\033[1;31m… while evaluating\033[0m frame %s\n' "$i"; done printf '\033[1;31merror:\033[0m src is an alias that throws\n' exit 1fiexec "$@"EOFchmod +x "$T/bin/devenv"# (The sandbox hides /tmp, where the stub lives.)export DELEGATE_SANDBOX_EXTRA="--ro-bind$T/bin$T/bin--bind$T/state$T/state"# Language caches under $T; secret/ stands for the rest of $HOME's caches.mkdir -p "$T/cache/gleam" "$T/cache/secret" "$T/data/pnpm/store" "$T/gomod" "$T/gobuild"echo s >"$T/cache/secret/x"export XDG_CACHE_HOME="$T/cache" XDG_DATA_HOME="$T/data" GOMODCACHE="$T/gomod" GOCACHE="$T/gobuild"export PATH="$T/bin:$PATH" DELEGATE_OPENCODE="$T/bin/fake-opencode" DELEGATE_DEVENV=0 DELEGATE_CHECK="" DELEGATE_MODEL=fake/model \ DELEGATE_AGENT_DEST="$T/agents/delegate.md" FAKE_STATE="$T/state"fake() { printf "%s\n" "$1" >"$T/bin/fake.sh"; export FAKE="$T/bin/fake.sh"; }
mkdir -p "$T/proj/sib" && echo hi >"$T/proj/sib/x"repo="$T/proj/repo"mkdir -p "$repo" && cd "$repo"jj git init >/dev/null 2>&1printf 'one\ntwo\nthree\n' >shared.txtprintf 'def add(a, b):\n return a - b\n' >calc.pyprintf '.devenv\n' >.gitignorejj commit -m init >/dev/null 2>&1jj bookmark create main -r @- >/dev/null 2>&1
echo "# start / run / diff / report"expect 0 "start a" -- delegate start a <<<"Fix add() in calc.py"ws_a="$(cat "$T/out")"[ "$ws_a" = "$T/proj/repo-t-a" ] && ok "start prints the root" || bad "start printed '$ws_a'"[ ! -e "$ws_a/.git" ] && ok "task workspace isn't colocated" || bad "task workspace has .git"has "spec carries the trailer" "Delegate-Task: t-a" "$(desc t-a@-)"has "status: idle, no rounds" "^a idle 0 ok clean 0$" "$(delegate status --files)"expect 1 "report before any round" -- delegate report a
fake 'sed -i "s/a - b/a + b/" calc.py'expect 0 "run a" -- delegate run ahas "report on stdout" "REPORT fix=no" "$(cat "$T/out")"has "model passed" "--model fake/model" "$(cat "$T/out")"has "sandboxed runs are standalone" "--standalone" "$(cat "$T/out")"has "files on stderr" "calc.py" "$(cat "$T/err")"has "round 1 is its own change" "^round 1: spec" "$(desc t-a@-)"yes_ "spec change holds no files" '[ -z "$(J diff -r t-a@-- --name-only)" ]'has "status: 1 round, 1 file" "^a idle 1 ok clean 1$" "$(delegate status --files)"has "status --files lists it" "^ M calc.py$" "$(delegate status --files)"has "diff spans the task" "a + b" "$(delegate diff a)"has "report kept in the repo" "REPORT fix=no" "$(delegate report a)"has "status counts files" "^a idle 1 ok clean 1$" "$(delegate status)"hasnt "report without the trailer" "Delegate-Round" "$(delegate report a)"
fake 'echo "# sum" >>calc.py'expect 0 "fix round" -- delegate run a <<<"Add a commentdetails"has "fix section in the prompt" "REPORT fix=yes" "$(cat "$T/out")"has "round named after the fix's first line" "^round 2: Add a comment" "$(desc t-a@-)"has "round keeps the fix" "details" "$(desc t-a@-)"has "diff --last is the last round only" "# sum" "$(delegate diff a --last)"echo x >"$ws_a/extra.txt"has "diff limited to a path" "calc.py" "$(delegate diff a --name-only -- calc.py)"hasnt "diff limited to a path: nothing else" "extra.txt" "$(delegate diff a --name-only calc.py)"has "diff --name-only" "extra.txt" "$(delegate diff a --name-only)"rm "$ws_a/extra.txt"hasnt "diff --last excludes round 1" "^+ return a + b" "$(delegate diff a --last)"has "status: 2 rounds" "^a idle 2 " "$(delegate status --files)"has "report: latest round" "REPORT fix=yes" "$(delegate report a)"has "report --round 1" "REPORT fix=no" "$(delegate report a --round 1)"expect 1 "report: no round 3" -- delegate report a --round 3echo "by hand" >"$ws_a/hand.txt"fake 'echo r3 >r3.txt'expect 0 "a round after hand edits" -- delegate run a <<<"Round three"has "says it kept the hand edits apart" "kept the edits made in t-a's workspace" "$(cat "$T/err")"hasnt "the round doesn't list them" "hand.txt" "$(sed -n '/changed 1 files/,$p' "$T/err")"has "the round lists its own" "A r3.txt" "$(cat "$T/err")"has "round numbering skips the edits" "^round 3: Round three" "$(desc t-a@-)"has "the edits are their own change" "^edits by hand between rounds" "$(desc t-a@--)"has "with the hand file" "hand.txt" "$(J diff -r t-a@-- --name-only)"has "status counts 3 rounds" "^a idle 3 " "$(delegate status)"has "report --round 3 is round 3" "REPORT fix=yes" "$(delegate report a --round 3)"hasnt "diff --last is round 3 only" "hand.txt" "$(delegate diff a --last --name-only)"has "diff has both" "hand.txt" "$(delegate diff a --name-only)"
[ -f "$T/agents/delegate.md" ] && ok "agent installed before the run" || bad "agent not installed"p="$(cat "$T/state/prompt")"has "rule: conflicting requirements" "When two requirements conflict" "$p"has "rule: main checkout readable" "coordinator's checkout at $repo" "$p"has "rule: no repo, no nix builds" "nix build, nix flake check" "$p"has "rule: lockfiles are fine" "Editing manifests and lockfiles" "$p"has "rule: nix edits don't reach the shell" "doesn't reach it" "$p"hasnt "no check rule without a check" "run exactly" "$p"
echo "# model"fake 'true'FAKE_USED=other/model expect 1 "a different model answering fails the run" -- delegate run ahas "names both models" "asked for fake/model#default but .* other/model#default" "$(cat "$T/err")"has "the round is marked untrusted" "^Untrusted: " "$(desc t-a@-)"DELEGATE_MODEL= expect 2 "no model refuses to run" -- delegate run ahas "a refused run leaves no round" "^a idle 4 " "$(delegate status --files)"expect 0 "--model overrides" -- delegate run a --model fake/otherhas "--model passed" "--model fake/other" "$(cat "$T/out")"
fake 'true'expect 0 "empty round" -- delegate run a <<<"Nothing to do"has "an empty round still counts" "^a idle 6 " "$(delegate status --files)"has "and keeps its report" "REPORT fix=yes" "$(delegate report a)"has "says it changed nothing" "changed nothing" "$(cat "$T/err")"
echo "# sandbox"delegate start z <<<"another task" >/dev/null 2>&1echo work >"$T/proj/repo-t-z/z.txt"fake 'touch inside; touch ../escape ~/escape 2>/dev/nullcat ../sib/x >/dev/null 2>&1 && touch saw-sibtouch ../sib/w 2>/dev/null && touch wrote-sibcat ../repo/calc.py >/dev/null 2>&1 && touch saw-maintouch ../repo/w 2>/dev/null && touch wrote-main[ -z "$(ls -A ../repo/.jj)" ] && touch main-jj-empty[ ! -d ../repo/.git ] || [ -z "$(ls -A ../repo/.git)" ] && touch main-git-empty[ ! -e ../repo-t-z/z.txt ] && touch other-task-hiddenjj log >/dev/null 2>&1 && touch jj-works; git status >/dev/null 2>&1 && touch git-works[ "$GOMODCACHE" = "'"$T/gomod"'" ] && echo m >"$GOMODCACHE/m" && touch gomod[ "$GOCACHE" = "'"$T/gobuild"'" ] && echo b >"$GOCACHE/b" && touch gocacheecho g >"$XDG_CACHE_HOME/gleam/g" && touch gleamecho p >"$XDG_DATA_HOME/pnpm/store/p" && touch pnpm[ ! -e "$XDG_CACHE_HOME/secret/x" ] && touch secret-hiddentrue'expect 0 "sandboxed run" -- delegate run a[ -e "$ws_a/inside" ] && ok "workspace is writable" || bad "workspace not writable"[ ! -e "$T/proj/escape" ] && [ ! -e "$HOME/escape" ] && ok "nothing written outside" || bad "wrote outside the workspace"for f in saw-sib saw-main main-jj-empty main-git-empty other-task-hidden gomod gocache gleam pnpm secret-hidden; do [ -e "$ws_a/$f" ] && ok "sandbox: $f" || bad "sandbox: no $f"donefor f in wrote-sib wrote-main jj-works git-works; do [ ! -e "$ws_a/$f" ] && ok "sandbox: no $f" || bad "sandbox: $f"; donefor f in gomod/m gobuild/b cache/gleam/g data/pnpm/store/p; do [ -e "$T/$f" ] && ok "cache write kept: $f" || bad "cache write lost: $f"; donedelegate drop z >/dev/null 2>&1
echo "# long sessions and crashes"fake 'echo long >long.txt'FAKE_SPAM=200000 expect 0 "a long session (many events) doesn't crash" -- delegate run a <<<"long one"has "its report is printed" "REPORT fix=yes" "$(cat "$T/out")"has "and kept" "REPORT fix=yes" "$(delegate report a)"r0="$(delegate status | awk '$1 == "a" { print $3 }')"fake 'sleep 3; echo late >late.txt'delegate run a >/dev/null 2>"$T/err.bg" &bg=$!sleep 1kill -TERM "$bg"rc=0; wait "$bg" || rc=$?[ "$rc" = 143 ] && ok "SIGTERM: exit 143" || bad "SIGTERM: exit $rc"has "SIGTERM: says it sealed the round" "sealed it" "$(cat "$T/err.bg")"has "SIGTERM: round sealed and marked" "^Interrupted: " "$(desc t-a@-)"has "SIGTERM: idle, one more round" "^a idle $((r0 + 1)) " "$(delegate status)"yes_ "SIGTERM: the round's edits are kept" '[ -n "$(J diff -r t-a@- --name-only)" ]'fake 'sleep 3'FAKE_EARLY=partial delegate run a >/dev/null 2>&1 &bg=$!sleep 1kill -KILL "$bg"; wait "$bg" 2>/dev/null || truefor _ in $(seq 40); do delegate status | grep -q '^a running' || break; sleep 0.25; donehas "SIGKILL: status says interrupted" "^a interrupted $((r0 + 2)) " "$(delegate status)"has "SIGKILL: report recovers the last message" "EARLY partial" "$(delegate report a 2>&1)"has "SIGKILL: report says it was interrupted" "was interrupted" "$(delegate report a 2>&1)"expect 1 "SIGKILL: land refuses an interrupted round" -- delegate land a -m xfake 'true'expect 0 "the next run seals it first" -- delegate run ahas "says so" "which was interrupted" "$(cat "$T/err")"has "SIGKILL: sealed round marked" "^Interrupted: " "$(desc t-a@--)"has "SIGKILL: sealed with the recovered message" "EARLY partial" "$(desc t-a@--)"has "and the new round follows" "^a idle $((r0 + 3)) " "$(delegate status)"
echo "# locks and timeout"fake 'sleep 5'delegate run a >/dev/null 2>&1 &bg=$!sleep 1has "status: running, no behind" "^a running [0-9]* - " "$(delegate status --files)"expect 75 "second run refused" -- delegate run aexpect 75 "land refused while running" -- delegate land a -m xexpect 75 "drop refused while running" -- delegate drop await $bg || truefake 'sleep 30'expect 124 "timeout exits 124" -- delegate run a --timeout 1shas "a timed-out round says so" "timed out" "$(delegate report a)"expect 0 "drop a" -- delegate drop ahasnt "dropped from status" "^a " "$(delegate status --files)"[ ! -e "$ws_a" ] && ok "workspace deleted" || bad "workspace still there"
echo "# two tasks on the same lines"fake 'sed -i "s/two/TWO-b/" shared.txt'delegate start b <<<"b edits line two" >/dev/null 2>&1delegate run b >/dev/null 2>&1fake 'echo "b round 2" >b.txt'delegate run b <<<"also add b.txt" >/dev/null 2>&1fake 'sed -i "s/two/TWO-c/" shared.txt'delegate start c <<<"c edits line two" >/dev/null 2>&1delegate run c >/dev/null 2>&1st="$(delegate status --files)"has "overlap listed" "shared.txt b,c\|shared.txt c,b" "$st"old="$(cid main)"expect 0 "land b (two rounds)" -- delegate land b -m "feat: b"land_one "$old" "land b"[ "$(desc main)" = "feat: b" ] && ok "landed change has only the message" || bad "main's description: $(desc main)"has "both rounds in it" "b.txt" "$(J diff -r main --name-only)"yes_ "coordinator's working copy follows main" '[ "$(jj log --no-graph -r @- -T commit_id)" = "$(cid main)" ]'has "says so" "moved your working copy" "$(cat "$T/err")"has "c conflicted after b landed" "^c idle 1 ok conflicted 1$" "$(delegate status --files)"expect 3 "land c refused: conflicts" -- delegate land c -m "feat: c"has "conflict names the trunk change" "feat: b" "$(cat "$T/err")"fake 'printf "one\nTWO-b-and-c\nthree\n" >shared.txt'expect 0 "resolve round" -- delegate run c <<<"Resolve the conflict in shared.txt"has "c clean after resolve" "^c idle 2 ok clean 1$" "$(delegate status --files)"echo wip >"$repo/wip.txt"old="$(cid main)"expect 0 "land c" -- delegate land c -m "feat: c"land_one "$old" "land c"has "c on main" "TWO-b-and-c" "$(J file show -r main shared.txt)"yes_ "a working copy with other edits is rebased onto main" '[ "$(jj log --no-graph -r @- -T commit_id)" = "$(cid main)" ] && [ "$(jj diff --name-only)" = wip.txt ]'has "says so" "rebased your working copy" "$(cat "$T/err")"hasnt "the edits didn't land" "wip.txt" "$(J diff -r main --name-only)"rm "$repo/wip.txt"
echo "# commit on trunk"echo base >"$repo/base.txt"echo other >"$repo/other.txt"old="$(cid main)"expect 0 "commit one path on main" -- delegate commit -m "chore: base" base.txtland_one "$old" "commit"has "main is the commit" "chore: base" "$(desc main)"hasnt "only the path given" "other.txt" "$(J diff -r main --name-only)"yes_ "the rest stays in the working copy" '[ "$(jj log --no-graph -r @- -T commit_id)" = "$(cid main)" ] && [ -n "$(jj diff --name-only)" ]'rm "$repo/other.txt"expect 1 "commit refuses an empty working copy" -- delegate commit -m xjj new main- >/dev/null 2>&1 && echo x >"$repo/off.txt"expect 1 "commit refuses off main" -- delegate commit -m xhas "hint: rebase" "jj rebase -b @ -d main" "$(cat "$T/err")"jj abandon @ >/dev/null 2>&1 && jj new main >/dev/null 2>&1echo bump >"$repo/bump.txt" && jj commit -m "chore: my bump" >/dev/null 2>&1echo x >"$repo/off.txt"expect 1 "commit refuses on your own commits above main" -- delegate commit -m xhas "hint: names them" "chore: my bump" "$(cat "$T/err")"has "hint: move main up" "jj bookmark set main -r @-" "$(cat "$T/err")"hasnt "hint: not the no-op rebase" "jj rebase -b" "$(cat "$T/err")"rm "$repo/off.txt"; jj abandon @- >/dev/null 2>&1; jj new main >/dev/null 2>&1
echo "# check and preflight"export DELEGATE_CHECK='echo x >>"$FAKE_STATE/count"; cat ../sib/x >/dev/null'expect 0 "preflight: a check reading a sibling passes" -- delegate preflighthas "says it passes" "passes on main" "$(cat "$T/err")"[ ! -e "$T/proj/repo-delegate-preflight" ] && ok "preflight workspace removed" || bad "preflight workspace left": >"$T/state/count"delegate start p1 <<<"p1" >/dev/null 2>&1delegate start p2 <<<"p2" >/dev/null 2>&1[ "$(wc -l <"$T/state/count")" = 0 ] && ok "cached: starts don't re-run it" || bad "preflight ran $(wc -l <"$T/state/count") times"expect 0 "preflight --force runs it again" -- delegate preflight --force[ "$(wc -l <"$T/state/count")" = 1 ] && ok "ran once" || bad "ran $(wc -l <"$T/state/count") times"delegate drop p1 >/dev/null 2>&1; delegate drop p2 >/dev/null 2>&1
export DELEGATE_CHECK='cat "$XDG_CACHE_HOME/secret/x"'expect 5 "start refused: check fails on trunk in the sandbox" -- delegate start q <<<"q"has "says why" "fails on main itself" "$(cat "$T/err")"has "shows the check's output" "secret/x" "$(cat "$T/err")"[ ! -e "$T/proj/repo-t-q" ] && ok "no workspace left" || bad "t-q left behind"expect 0 "--no-preflight starts anyway" -- delegate start q --no-preflight <<<"q"fake 'true'delegate run q >/dev/null 2>&1has "rule: run exactly the check" 'run exactly `cat' "$(cat "$T/state/prompt")"has "rule: stop when it isn't yours" "don't work around it" "$(cat "$T/state/prompt")"expect 5 "land: trunk fails too" -- delegate land q -m "feat: q"has "not the task's fault" "not t-q's fault" "$(cat "$T/err")"delegate drop q >/dev/null 2>&1
export DELEGATE_CHECK='touch artefact'expect 5 "preflight: a check that adds tracked files fails" -- delegate preflighthas "names the file" "artefact" "$(cat "$T/err")"
fake 'echo new >new.txt'DELEGATE_CHECK= delegate start d <<<"d adds new.txt" >/dev/null 2>&1delegate run d >/dev/null 2>&1DELEGATE_CHECK="false" expect 4 "failing check refuses land" -- delegate land d -m "feat: d"has "hint: preflight" "delegate preflight" "$(cat "$T/err")"DELEGATE_CHECK='for i in $(seq 100); do printf "\033[31mline %s\033[0m\n" $i; done; false' expect 4 "a long check failure" -- delegate land d -m "feat: d"has "shows the end" "^line 100$" "$(cat "$T/err")"hasnt "not all of it" "^line 50$" "$(cat "$T/err")"has "names the full log" "all of them in .*t-d-check.log" "$(cat "$T/err")"hasnt "no colour codes" $'\033' "$(cat "$T/err")"expect 4 "check that adds tracked files refuses land" -- delegate land d -m "feat: d"rm -f "$T/proj/repo-t-d/artefact"export DELEGATE_CHECK='echo x >>"$FAKE_STATE/count"; test -f new.txt'old="$(cid main)"expect 0 "passing check lands" -- delegate land d -m "feat: d"land_one "$old" "land d": >"$T/state/count"expect 0 "land records a pass for the new main" -- delegate preflight[ "$(wc -l <"$T/state/count")" = 0 ] && ok "no preflight run needed" || bad "preflight ran again"export DELEGATE_CHECK=""
echo "# post-land"fake 'echo g >g.txt'delegate start g <<<"g adds g.txt" >/dev/null 2>&1delegate run g >/dev/null 2>&1old="$(cid main)"DELEGATE_POST_FIX='echo h >hash.txt' DELEGATE_POST_CHECK='test -f hash.txt && test -f g.txt' expect 0 "land with post-land fix and check" -- delegate land g -m "feat: g"land_one "$old" "land g"has "says what the fix folded" "folded the post-land fix" "$(cat "$T/err")"has "and that the check passed" "post-land check passed" "$(cat "$T/err")"has "the fix is in the landed change" "hash.txt" "$(J diff -r main --name-only)"has "with the task's files" "g.txt" "$(J diff -r main --name-only)"yes_ "working copy is empty on main" '[ "$(jj log --no-graph -r @- -T commit_id)" = "$(cid main)" ] && [ -z "$(jj diff --name-only)" ]'
fake 'echo i >i.txt'delegate start i <<<"i" >/dev/null 2>&1delegate run i >/dev/null 2>&1DELEGATE_POST_FIX=true DELEGATE_POST_CHECK=false expect 6 "post-land check failure exits 6" -- delegate land i -m "feat: i"has "a fix that changed nothing says so" "post-land fix changed nothing" "$(cat "$T/err")"has "main moved anyway" "feat: i" "$(desc main)"has "says trunk is broken" "main is broken" "$(cat "$T/err")"[ ! -e "$T/proj/repo-t-i" ] && ok "workspace removed" || bad "t-i left"
fake 'echo k >k.txt'delegate start k <<<"k" >/dev/null 2>&1delegate run k >/dev/null 2>&1echo wip >"$repo/wip.txt"old="$(cid main)"DELEGATE_POST_FIX='echo h2 >hash.txt' expect 0 "post-land with unrelated edits in the working copy" -- delegate land k -m "feat: k"land_one "$old" "land k"has "the fix is folded" "^h2$" "$(J file show -r main hash.txt)"hasnt "the edits aren't" "wip.txt" "$(J diff -r main --name-only)"yes_ "the edits stay in the working copy, on main" '[ "$(jj log --no-graph -r @- -T commit_id)" = "$(cid main)" ] && [ "$(jj diff --name-only)" = wip.txt ]'
fake 'echo m >m.txt'delegate start m <<<"m" >/dev/null 2>&1delegate run m >/dev/null 2>&1echo mine >"$repo/m.txt"old="$(cid main)"DELEGATE_POST_CHECK=true expect 1 "post-land: edits to the task's files refuse up front" -- delegate land m -m "feat: m"has "names the file" "m.txt" "$(cat "$T/err")"[ "$(cid main)" = "$old" ] && ok "nothing landed" || bad "main moved"expect 0 "without post-land steps it lands" -- delegate land m -m "feat: m"yes_ "and the working copy stays on the old main" '[ "$(jj log --no-graph -r @- -T commit_id)" = "$old" ]'has "saying why" "stays on the old main" "$(cat "$T/err")"rm "$repo/m.txt" "$repo/wip.txt" && jj new main >/dev/null 2>&1
fake 'echo n >n.txt'delegate start n <<<"n" >/dev/null 2>&1delegate run n >/dev/null 2>&1jj new main- >/dev/null 2>&1DELEGATE_POST_CHECK=true expect 1 "post-land: working copy off trunk refuses up front" -- delegate land n -m "feat: n"jj new main >/dev/null 2>&1echo mine >"$repo/hash.txt"DELEGATE_POST_FIX='echo h3 >hash.txt' expect 6 "post-land fix touching your edited file folds nothing" -- delegate land n -m "feat: n"has "says so" "folded nothing" "$(cat "$T/err")"has "main keeps the old hash" "^h2$" "$(J file show -r main hash.txt)"jj restore >/dev/null 2>&1
echo "# renames"fake 'mkdir -p sub && mv shared.txt sub/moved.txt'delegate start r <<<"r moves shared.txt" >/dev/null 2>&1expect 0 "run r (a move)" -- delegate run rhas "run lists the move" "R shared.txt -> sub/moved.txt" "$(cat "$T/err")"has "status --files too" "^ R shared.txt -> sub/moved.txt$" "$(delegate status --files)"fake 'echo x >>shared.txt'delegate start r2 <<<"r2 edits shared.txt" >/dev/null 2>&1delegate run r2 >/dev/null 2>&1has "overlap counts the old path" "shared.txt r,r2\|shared.txt r2,r" "$(delegate status)"echo mine >>"$repo/shared.txt"DELEGATE_POST_CHECK=true expect 1 "land: your edit to a moved file refuses up front" -- delegate land r -m "feat: r"has "names it" "shared.txt" "$(cat "$T/err")"jj restore >/dev/null 2>&1delegate drop r >/dev/null 2>&1; delegate drop r2 >/dev/null 2>&1
echo "# a round that breaks the devenv shell"fake 'echo broken >broken.nix'delegate start s <<<"s edits nix" >/dev/null 2>&1DELEGATE_DEVENV=1 expect 4 "the round's check can't tell; delegate can" -- delegate run shas "the report is still printed" "REPORT" "$(cat "$T/out")"has "says the shell doesn't evaluate" "t-s's devenv shell doesn't evaluate after round 1" "$(cat "$T/err")"has "shows the error" "src is an alias" "$(cat "$T/err")"hasnt "without colour codes" $'\033' "$(cat "$T/err")"has "the round says so" "^Shell broken: .*src is an alias" "$(desc t-s@-)"DELEGATE_DEVENV=1 DELEGATE_CHECK=true expect 4 "land: says the shell is broken, not just check failed" -- delegate land s -m "feat: s"has "says so" "t-s's devenv shell doesn't evaluate" "$(cat "$T/err")"fake 'grep -q "the devenv shell is broken" "$FAKE_STATE/prompt" && grep -q "src is an alias" "$FAKE_STATE/prompt" && echo fixed >broken.nix'DELEGATE_DEVENV=1 expect 0 "a fix round runs without the shell, told to fix it" -- delegate run s <<<"Fix the nix file"has "the prompt said so (the fix landed)" "^fixed$" "$(cat "$T/proj/repo-t-s/broken.nix")"has "says it evaluates again" "evaluates again" "$(cat "$T/err")"hasnt "the round isn't marked" "Shell broken" "$(desc t-s@-)"delegate drop s >/dev/null 2>&1
echo "# stacked"fake 'echo base >stack.txt'delegate start e <<<"e makes stack.txt" >/dev/null 2>&1delegate run e >/dev/null 2>&1fake 'echo more >>stack.txt'expect 0 "start f on e" -- delegate start f --on 't-e@-' <<<"f extends stack.txt"delegate run f >/dev/null 2>&1has "f diff is only its own" "+more" "$(delegate diff f)"hasnt "f diff excludes e" "+base" "$(delegate diff f)"expect 1 "f can't land before e" -- delegate land f -m "feat: f"fake 'echo fixed >other.txt'delegate run e <<<"one more line" >/dev/null 2>&1old="$(cid main)"expect 0 "land e" -- delegate land e -m "feat: e"land_one "$old" "land e"has "f follows e onto main" "^f idle 1 ok " "$(delegate status --files)"old="$(cid main)"expect 0 "land f" -- delegate land f -m "feat: f"land_one "$old" "land f"has "stack on main" "more" "$(J file show -r main stack.txt)"[ -z "$(delegate status --files)" ] && ok "no tasks left" || bad "tasks left: $(delegate status)"
echo "# baked settings": >"$T/state/devenv"h="$(for f in devenv.nix devenv.yaml devenv.local.nix devenv.lock; do if [ -f "$repo/$f" ]; then sha256sum <"$repo/$f" | cut -c1-64; else echo -; fi; done | sha256sum | cut -c1-64)"DELEGATE_CONFIG_HASH="$h" expect 0 "config unchanged: runs as is" -- delegate status[ ! -s "$T/state/devenv" ] && ok "no fresh shell" || bad "ran devenv"echo '{ ... }: { }' >"$repo/devenv.nix"DELEGATE_CONFIG_HASH="$h" expect 0 "config changed: runs again in a fresh devenv shell" -- delegate status[ -s "$T/state/devenv" ] && ok "through devenv" || bad "devenv not run"has "says so" "fresh devenv shell" "$(cat "$T/err")"has "still stale there: warns, no loop" "may be stale" "$(cat "$T/err")"rm "$repo/devenv.nix"
echoecho "$pass passed, $fail failed"[ "$fail" = 0 ]