#!/usr/bin/env bash set -uo pipefail # test-runtime.sh — Verify the runtime survives source deletion and works from # an unrelated project (parent D9 proofs 3, 4, 5). # 3. repo deletable after install: install into temp HOME; rm -rf the source # copy; everything below still works. # 4. opencode start from unrelated project (harnessed with a fake OPENCODE_BIN; # real launch is Tester's clean-room job). # 5. repo bootstrap works: from unrelated project, runtime repo-bootstrap.sh # status|bootstrap creates .opencode/ with markers; rerun idempotent. # # TDD: tests written (and failing) BEFORE the install.sh runtime-tree install. # They run install.sh with a temp HOME and a temp source copy, so they exercise # the REAL runtime-tree logic. Fail until install.sh installs bin/ + skills/. # # Requires: this suite must run against a source tree (TEAM_ROOT default = repo # checkout) so it can copy the source into a temp dir and install from it. # # Exit codes: 0 = all pass, 1 = any failure. TEAM_ROOT="${TEAM_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" SRC_SCRIPTS="$TEAM_ROOT/scripts" RUNTIME_ROOT_DEFAULT="$HOME/.config/opencode/dev-agent-team" PASS=0; FAIL=0 T="$(mktemp -d)" trap 'rm -rf "$T"' EXIT ok() { PASS=$((PASS+1)); echo "PASS $1"; } fail(){ FAIL=$((FAIL+1)); echo "FAIL $1 — $2" >&2; } assert_file() { [ -f "$1" ] && return 0; return 1; } assert_dir() { [ -d "$1" ] && return 0; return 1; } # ======================================================================== # # Setup: copy the source checkout into a temp HOME, define an isolated env # ======================================================================== # SRC="$T/source" # source copy (will be deleted post-install) HOME_T="$T/home" # throwaway HOME RUNTIME="$T/runtime" # runtime root override AGENTS_T="$T/agents" # agents target override mkdir -p "$SRC" "$HOME_T" cp -r "$TEAM_ROOT/agents" "$SRC/agents" mkdir -p "$SRC/scripts" cp -p "$SRC_SCRIPTS"/*.sh "$SRC/scripts/" 2>/dev/null || true mkdir -p "$SRC/skills" cp -r "$TEAM_ROOT/skills/." "$SRC/skills/" 2>/dev/null || true mkdir -p "$SRC/improvements" cp -r "$TEAM_ROOT/improvements/." "$SRC/improvements/" 2>/dev/null || true # ======================================================================== # # TEST T01: install.sh installs the full runtime tree into temp env # ======================================================================== # INST_OUT="$(env -i HOME="$HOME_T" \ OPENCODE_AGENTS_DIR="$AGENTS_T" \ OPENCODE_DEV_AGENT_TEAM="$RUNTIME" \ bash "$SRC/scripts/install.sh" 2>&1 || echo "INSTALL_RC=$?")" BIN_RUNTIME="$RUNTIME/bin" if assert_dir "$BIN_RUNTIME" \ && assert_file "$BIN_RUNTIME/memory-lifecycle.sh" \ && assert_file "$BIN_RUNTIME/repo-bootstrap.sh" \ && assert_file "$BIN_RUNTIME/verify-permission-patterns.sh" \ && assert_dir "$RUNTIME/skills" \ && [ "$(find "$RUNTIME/skills" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)" = "12" ] \ && assert_file "$RUNTIME/skills/SKILLS.md" \ && assert_dir "$RUNTIME/improvements" \ && assert_file "$RUNTIME/install-manifest.json"; then ok "T01 install.sh installs runtime tree (bin/ memory-lifecycle.sh repo-bootstrap.sh verify-permission-patterns.sh, skills 12+SKILLS.md, improvements, manifest)" else fail "T01 install.sh installs full runtime tree" "missing runtime artifacts under $RUNTIME (install output: $INST_OUT)" fi # ======================================================================== # # TEST T02: 14 agents copied to the target # ======================================================================== # N_AG="$(( $(find "$AGENTS_T" -maxdepth 1 -name '*.md' 2>/dev/null | wc -l) ))" if [ "$N_AG" = "14" ]; then ok "T02 14 agents copied to agents target ($N_AG/14)" else fail "T02 14 agents copied to agents target" "only $N_AG/14 at $AGENTS_T" fi # ======================================================================== # # TEST T03: source checkout can be deleted; runtime tree remains # ======================================================================== # rm -rf "$SRC" if [ -d "$SRC" ]; then fail "T03 source checkout deletable" "source still present" elif assert_dir "$BIN_RUNTIME" && assert_dir "$RUNTIME/skills" && assert_dir "$RUNTIME/improvements" \ && assert_file "$RUNTIME/install-manifest.json"; then ok "T03 source checkout deletable; runtime tree intact" else fail "T03 source checkout deletable; runtime tree intact" "runtime damaged after source removal" fi # ======================================================================== # # TEST T04: runtime memory-lifecycle.sh works from an unrelated project # ======================================================================== # PROJ="$T/project1" mkdir -p "$PROJ/memory/lessons" git -C "$PROJ" init -q . 2>/dev/null || true printf 'x\n' > "$PROJ/f.txt" printf -- '%s\n' "# L" "runtime lesson" > "$T/runlesson.md" ( cd "$PROJ" && bash "$BIN_RUNTIME/memory-lifecycle.sh" store lessons "$T/runlesson.md" >/dev/null 2>&1 ) if assert_file "$PROJ/memory/lessons/runlesson.md"; then ok "T04 runtime memory-lifecycle.sh works from unrelated project" else fail "T04 runtime memory-lifecycle.sh works from unrelated project" "not stored in $PROJ/memory" fi # ======================================================================== # # TEST T05: runtime repo-bootstrap.sh bootstrap builds .opencode/ with markers # ======================================================================== # mkdir -p "$PROJ" printf '{}\n' > "$PROJ/package.json" BOOT_RC=0 ( cd "$PROJ" && bash "$BIN_RUNTIME/repo-bootstrap.sh" bootstrap >/dev/null 2>&1 ) || BOOT_RC=$? if [ "$BOOT_RC" = "0" ] \ && assert_file "$PROJ/.opencode/AGENTS.md" \ && assert_file "$PROJ/.opencode/.bootstrap-meta" \ && assert_dir "$PROJ/.opencode/skills/repo-context"; then ok "T05 runtime repo-bootstrap.sh bootstrap creates .opencode/ with markers" else fail "T05 runtime repo-bootstrap.sh bootstrap" "rc=$BOOT_RC; artifacts missing" fi # ======================================================================== # # TEST T06: bootstrap idempotent (second run no changes) # ======================================================================== # M1="$(cat "$PROJ/.opencode/.bootstrap-meta" 2>/dev/null)" A1="$(cat "$PROJ/.opencode/AGENTS.md" 2>/dev/null)" ( cd "$PROJ" && bash "$BIN_RUNTIME/repo-bootstrap.sh" bootstrap >/dev/null 2>&1 ) M2="$(cat "$PROJ/.opencode/.bootstrap-meta" 2>/dev/null)" A2="$(cat "$PROJ/.opencode/AGENTS.md" 2>/dev/null)" if [ "$M1" = "$M2" ] && [ "$A1" = "$A2" ]; then ok "T06 runtime bootstrap rerun is idempotent" else fail "T06 runtime bootstrap rerun is idempotent" "content changed on second run" fi # ======================================================================== # # TEST T07: runtime repo-bootstrap.sh status on fresh and stale # ======================================================================== # ST1=0; ( cd "$PROJ" && bash "$BIN_RUNTIME/repo-bootstrap.sh" status >/dev/null 2>&1 ) || ST1=$? printf '/* changed */\n' > "$PROJ/package.json" ST2=0; ( cd "$PROJ" && bash "$BIN_RUNTIME/repo-bootstrap.sh" status >/dev/null 2>&1 ) || ST2=$? if [ "$ST1" = "0" ] && [ "$ST2" = "1" ]; then ok "T07 runtime bootstrap status: fresh(0) then stale(1)" else fail "T07 runtime bootstrap status" "fresh_rc=$ST1 stale_rc=$ST2" fi # ======================================================================== # # TEST T08: opencode start harness — fake OPENCODE_BIN invoked with env # ======================================================================== # FAKE="$T/fake-opencode" cat > "$FAKE" <<'EOF' #!/usr/bin/env bash echo "opencode invoked from $(pwd)"; echo "PWD=$PWD" EOF chmod +x "$FAKE" mkdir -p "$PROJ" HAR_OUT="$(cd "$PROJ" && OPENCODE_BIN="$FAKE" bash -c 'echo harness' 2>&1 || true)" # The harness verifies the runtime env var is exported; simulate a launch that # reads OPENCODE_DEV_AGENT_TEAM. HAR2="$(cd "$PROJ" && OPENCODE_DEV_AGENT_TEAM="$RUNTIME" bash -c 'echo "env=$OPENCODE_DEV_AGENT_TEAM"' 2>&1)" if echo "$HAR2" | grep -q "env=$RUNTIME"; then ok "T08 runtime env var propagates into unrelated-project launch context" else fail "T08 runtime env var propagates into launch context" "got: $HAR2" fi # ======================================================================== # # TEST T09: runtime tree runs from an unrelated project (all bins usable) # ======================================================================== # # All three runtime executables must run (via --help / status) from a project # whose cwd is unrelated to any runtime install location. ML_RC=0; ( cd "$PROJ" && bash "$BIN_RUNTIME/memory-lifecycle.sh" --help >/dev/null 2>&1 ) || ML_RC=$? RB_RC=0; ( cd "$PROJ" && bash "$BIN_RUNTIME/repo-bootstrap.sh" --help >/dev/null 2>&1 ) || RB_RC=$? VP_RC=0; ( cd "$PROJ" && bash "$BIN_RUNTIME/verify-permission-patterns.sh" >/dev/null 2>&1 ) || VP_RC=$? if [ -x "$BIN_RUNTIME/memory-lifecycle.sh" ] \ && [ -x "$BIN_RUNTIME/repo-bootstrap.sh" ] \ && [ -x "$BIN_RUNTIME/verify-permission-patterns.sh" ] \ && [ "$ML_RC" -le 2 ] && [ "$RB_RC" -le 2 ] && [ "$VP_RC" -le 2 ]; then ok "T09 all runtime bins executable and runnable from unrelated project" else fail "T09 all runtime bins executable/runnable" "ml=$ML_RC rb=$RB_RC vp=$VP_RC" fi # ======================================================================== # # TEST T10: skills readable from an unrelated project via agent-resolved path # ======================================================================== # # After source deletion, agents resolve skills through $OPENCODE_DEV_AGENT_TEAM. READ_OK=0 if [ -f "$RUNTIME/skills/tdd/SKILL.md" ] && grep -q "^## When to use" "$RUNTIME/skills/tdd/SKILL.md"; then READ_OK=1 fi if [ "$READ_OK" = "1" ]; then ok "T10 runtime skill readable via OPENCODE_DEV_AGENT_TEAM-resolved path" else fail "T10 runtime skill readable via resolved path" "missing $RUNTIME/skills/tdd/SKILL.md" fi # ======================================================================== # # Summary # ======================================================================== # echo echo "==================== SUMMARY ====================" echo "PASS: $PASS FAIL: $FAIL" [ "$FAIL" = "0" ] && echo "RESULT: ALL PASS" || echo "RESULT: FAILURES PRESENT" exit $(( FAIL > 0 ? 1 : 0 ))