Run (and extend) the reusable end-to-end test harness for board-steward. Use when the user wants to verify a board-steward overhaul end-to-end — multi-board routing/isolation, reconciliation (gating + real-Haiku moves), or a future feature — WITHOUT polluting their live board. Triggers: "run e2e", "/e2e", "test multi-board", "test recon end to end", "verify the overhaul", "regression test the board", "does routing/recon still work". The harness isolates all state, uses throwaway boards, and asserts the live board is untouched.
Resources
1Install
npx skillscat add malcolm1232/workboard/e2e Install via the SkillsCat registry.
This skill runs a reusable end-to-end test harness for board-steward to verify multi-board routing, isolation, and reconciliation. It prevents live board pollution by using isolated state and throwaway boards. Use it to regression-test architectural overhauls or verify new features without risking changes to the user's actual board data.
e2e — board-steward end-to-end test harness
A reusable harness that codifies the verification patterns proven during ARCH REDESIGN v2 (multi-board routing + reconciliation). Run it to regression-test those overhauls — or extend it for the next one — instead of hand-writing throwaway test scripts each time.
Source of the redesign it tests: ~/Desktop/WorkBoard/docs/ARCH_REDESIGN_V2.md.
The cardinal rule (why this exists)
A test must NEVER pollute the user's live board or global state. The harness enforces this structurally:
- Isolated state —
BOARD_REGISTRY/BOARD_ASSIGNMENTS/BOARD_ACTIVEare pointed at temp files;BOARD_NO_SERVER=1forces direct file writes for tests that don't need a server. The real~/.board-steward/is never touched. - Throwaway boards — every board under test is a temp dir, seeded in-memory or from
templates/board.json. - Live-board guard — the live board's (
:7891) card count is captured before the run and asserted unchanged after. If a test touches it, the suite fails. - Cleanup — all temp dirs are removed at the end.
How to run
python3 ~/.claude/skills/e2e/e2e_workboard.py multiboard # routing + $HOME disambiguation (no LLM, free)
python3 ~/.claude/skills/e2e/e2e_workboard.py recon # only_discovered flag + gating + CLAUDECODE path (free)
python3 ~/.claude/skills/e2e/e2e_workboard.py recon-haiku # real Haiku: IP→done / IP→backlog (~1 Haiku call)
python3 ~/.claude/skills/e2e/e2e_workboard.py all # multiboard + recon (free tier)
python3 ~/.claude/skills/e2e/e2e_workboard.py all --haiku # everything, including the Haiku E2EOverride the repo with BOARD_REPO=/path/to/WorkBoard (default ~/Desktop/WorkBoard).
Exit code 0 = all passed, 1 = a failure (printed at the bottom), 2 = bad args / repo not found.
Test catalog (what each group proves)
multiboard — the v2 multi-board work
routing_isolation— a card added from inside project A lands on A, never B;last-activetracks the last-mutated board.disambiguation— at$HOME, the last-active board wins over a newer-mtime board; mtime is only the fallback (the wrong-board bug that started v2).
recon (free) — the v2 reconciliation work
only_discovered_flag—reconcile_sweep(only_discovered=True)scopes to bootstrap-mined cards;Falsereconciles every non-done card (so SessionStart catches live cards).gates_short_circuit—--reconcile-onlymakes no Haiku call when there are no non-done cards (Gate A) or no recorded project activity (Gate B).claudecode_path—CLAUDECODE=1writes a proserecon_pending.json(no Haiku); proves the spawn'senv -u CLAUDECODEis load-bearing for the autonomous Haiku path.
recon-haiku (costs ~1 Haiku call) — the real end-to-end
haiku_e2e— a realclaude -p --model haikupass moves a shipped In-Progress card → done and a "nvm/skip" card → backlog.
Extending for the NEXT overhaul
Add a function test_<group>_<name>(ctx) in e2e_workboard.py and register it in GROUPS. Use the context helpers — they keep the cardinal rule automatic:
ctx.board(cards=[...])→ a throwaway project board.json (passfrom_template=Truefor the real seed).ctx.card(num, column, title, tags=[...])→ a minimally-valid card dict (real cards always carry anid).ctx.assert_eq(name, got, want)/ctx.assert_true(name, cond, detail)→ record pass/fail.- State is already isolated in
Ctx.__init__; the live-board guard runs automatically.
Always: isolate state, use throwaway boards, and never assume the live server's state. If a feature needs a running server, start it on an explicit free port (e.g. 7950) and verify :7891's count is unchanged.