--- name: dankvoice-bundle-swap-red-proof description: "Prove a dankvoice behavior test is red against the pre-change bundle by pointing DV_EXT at git show HEAD:dist/index.js, with the driver extracted from the bats heredoc into a pinned scratch directory. Use before claiming a new bats assertion is red-capable." --- Prove a new `tests/behavior.bats` assertion is **red-capable** — that it fails against the *previous* build, not just passes against the new one — without stashing, branching, or editing `src/`. ## Why `tests/behavior.bats` is a black-box suite: the plugin bundle is loaded through `DV_EXT`, so a *different bundle* is the red side. Stashing the fix would break the `dist` byte-identity pin test and the shared worktree. ## Working recipe ```bash cd ~/Projects/dankvoice # or the feature worktree: ~/Projects/wt-dv-* bun run build # current src = the green side git show HEAD:dist/index.js > /tmp/old-dist.js # the red side (pre-change bundle) W=/tmp/dv-redproof # PINNED name; see traps below rm -rf "$W" && mkdir -p "$W/bin" cat > "$W/player.sh" <<'SH' #!/usr/bin/env bash printf '%s\n' "$*" >> "$DV_STUB_LOG" for arg in "$@"; do case "$arg" in *.wav) cp "$arg" "$DV_COPY" ;; esac; done sleep "${DV_HOLD:-0.1}" SH chmod +x "$W/player.sh" ln -s "$W/player.sh" "$W/bin/pw-play" printf '#!/usr/bin/env bash\nprintf "116\\talsa_output.usb-KT.0\\taudio/sink\\t*\\n"\n' > "$W/bin/wpctl" chmod +x "$W/bin/wpctl" # The driver lives inside the bats file as a heredoc: extract it verbatim. end=$(grep -n '^TS$' tests/behavior.bats | cut -d: -f1) sed -n "86,$((end - 1))p" tests/behavior.bats > "$W/driver.ts" export DV_EXT="$PWD/dist/index.js" DV_STUB="$W/player.sh" DV_STUB_LOG="$W/player.log" \ DV_COPY="$W/played.wav" DV_OMPCONFIG=/dev/null DANKVOICE_CONFIG="$W/settings.json" \ DV_MODELS="$W/models.yml" DV_WPCTL=x DV_SELECT="" for ext in /tmp/old-dist.js "$PWD/dist/index.js"; do rm -f "$W/player.log" "$DANKVOICE_CONFIG" DV_EXT="$ext" bun "$W/driver.ts" 2>&1 | grep -E '^PLAYS|^CALLS|^NOTIFY|^SETTINGS' done ``` The scenario name is the driver's third argv (`speak`, `auto`, `repeat`, `humanizecycle`, `badurl`, …); the bats `-f` test body's env (`DV_PLAYER_BIN`, `DV_SETTINGS_JSON`, `DANKVOICE_VERBOSITY`, …) is passed the same way. `DV_SELECT` holds the menu answers, `|`-separated, consumed in order. ## Traps, each one cost a turn - **The scratch directory must be pinned by name.** `ls -dt /tmp/tmp.*` picks up *other sessions'* temp dirs, and then `player.sh` and `driver.ts` live in different directories — the run silently does the wrong thing. - **The stub must append to `$DV_STUB_LOG`.** The driver counts player invocations from that file; a stub that only `sleep`s reports `PLAYS 0` for *both* bundles and the whole proof reads as "fix does nothing". - **`DV_EXT` is required.** Missing it yields `Cannot find package 'undefined'` from the driver, which looks like a broken driver, not a missing env var. - **`DV_SETTINGS_JSON` supports `__PORT__`** (the driver substitutes the fixture port), so a preseed can name a prefixed route — needed whenever the test is about *which* endpoint got resolved. - **Never run this while a `bats` run is in flight** in the same repo: `setup()` runs `bun run build`, so two runs clobber `dist/` and the pin test. ## Reading the result Red side must show the *defect* (`PLAYS 0`, `644`, a chat `CALL` with the wrong prefix, `Invalid URL`), green side the fix — assert on printed markers only (`PLAYS`, `CALLS`, `CALL `, `NOTIFY`, `SETTINGS`). `CALLS` counts `/audio/speech` calls only; the rewrite call appears as its own `CALL /…/chat/completions … sys=` line, which is how prompt-level knobs (verbosity, heckle) are observed. Prefer this over a synthetic leak: if the check already caught a real leftover/failure once, that event *is* the sensitivity proof — say so in the commit message instead of faking one.