Changelog¶
All notable changes to Bashcrawl are documented in this file.
The format is based on Keep a Changelog.
[3.2.0] - 2026-08-01 — TermForge: the emulator becomes a framework¶
The in-browser bash emulator is extracted into TermForge (termforge/), a
universal, zero-dependency terminal framework: one environment-agnostic kernel
(parser, VFS, Shell, Line protocol, TerminalView) now powers the browser game,
real terminal sessions, a lightweight telnet server, and custom dev/monitoring
tools. The web game is byte-identical (golden-transcript proven), saves are
compatible, and the terminal-core bash game is untouched.
Added¶
termforge/core/— the framework kernel as dual-mode files (classic<script>+ CJS, no build step):parser,vfs(with NEW read-only providers for live-data files),shell(hook spine: preExecute / interceptLine / postExecute / observePipeline / beforeCommand / execDispatch / postCommand; injectable clock/rng/base64),packs/posix+packs/flavour,protocol(the versioned Line contract,docs/schemas/terminal-protocol.v1.md),view+sinks/dom+sinks/ansi(one TerminalView replacing the duplicated story/arcade renderers), andinput(shared history/completion helpers + a LineEditor and byte decoder for stream hosts).- Node hosts (
termforge/node/):host-tty.js(play in a real terminal —make tty-demo) andhost-telnet.js+telnet-codec.js(a minimal RFC 854 subset with NAWS, session caps, idle kick, and an nc-friendly--rawmode —make telnet-demo; loopback-bound by default, seedocs/termforge/telnet-host.md). - Apps (
termforge/apps/):bashcrawl.js(the game as an App descriptor) andprocwatch/(the custom-tool reference: livenode:osmetrics mounted as read-only VFS provider files, browsable with the ordinary posix toolkit;demo.htmlruns it in a browser fromfile://). - JS test suite (
make test-js,node --test, zero npm deps): 62 tests — golden transcripts recorded against the pre-refactor emulator and replayed byte-identically under a pinned vm clock/rng, save-shape + legacy-save round-trips, kernel/pack/view/input units, telnet codec units and a real loopback integration. CI runs it (setup-node 20) plusmake lint-js(node --checkover every tracked JS file). - Vendor pipeline:
termforge/core/is mirrored file-for-file intoweb/assets/js/vendor/termforge/bymake web-build(scripts/vendor_termforge.py), byte-verified bidirectionally byvalidate_static_web.py, pytest, andmake web-test. agentwatch(termforge/apps/agentwatch/,make agentwatch) — an AI-agent task dashboard on the framework: a TaskSource interface projected asboard/agent <id>/feedcommands AND live read-only VFS files (cat agents/forge,grep -r failed .), over any host. Ships a deterministic demo fleet (state ticks once per read — no clock/randomness) and a JSONL adapter that turns the repo's real agent-playtest telemetry (logs/sessions/**) into a live board (make agentwatch ARGS="--data-dir logs/sessions").- uiText copy deck — the framework/content boundary made mechanical:
every brandable string a core pack emits routes through
Shell#text()/DEFAULT_UI_TEXTwith neutral defaults; the game's entire voice (legacy error strings, fortune deck, banner art, figlet default) moved intoGAME_UI_TEXTinruntime.js. A newcontent-separation.test.jsfails the build if game vocabulary appears anywhere undertermforge/core/.
Changed¶
web/assets/js/runtime.js(1852 → ~930 lines) is now the game assembly:class Runtime extends TermForge.Shellwith the full 74-entry handlers literal (still thevalidate_runtime_commands.pycontract — the validator is untouched) andinstallGameHooks()for quests, achievements, daily, trainer, pathfind, and encounters.game.js/arcade.jsshare one TerminalView + DomSink (the twin log renderers and fourescapeHtmlcopies collapse into one);arcade.jspasses{ bare: true }instead of poking the instance.defaultState()now declares two previously-implicit fields (prevCwd,stats.lastPipedIn); persisted-save key order shifts accordingly (additive; legacy saves load unchanged — fixture-proven).- Host CLI parsing now uses node's standard
util.parseArgs(allowNegativefor--no-color; engines floor is node >= 20.16); VFS provider mounts now list in their parent directory as file or directory according to the mount root's own type.
Fixed¶
- Committed
web/data/world.jsonwas stale after the #75 markdown unwrap (freshness test red from a clean checkout).
[3.1.0] - 2026-07-06 — The Great Reduction + Web Flagship¶
The repo is reduced to two player surfaces plus one harness: the pure-bash
terminal game (played with real cd/cat — no launcher) and a static browser
trainer generated from the same content, plus a lean MCP playtest harness that
drives the real bash game. Net: ~350 files and ~75k lines removed.
Added¶
- Web mode router (
web/assets/js/shell.js): top-nav Story · Arcade · Reference, first-visit landing overlay, hash routing (#/story…), Alt+½/3 shortcuts, XP bridge, toasts. - Practice Arcade (
web/assets/js/arcade.js): mini-game framework over the shared bash emulator — each game is (seed world + goal predicate + scoring) on a scopedRuntime. Four games: Path Navigator (navigate the real dungeon), grep/find Hunt (find the sigil-bearing file), Pipe Puzzle (transform INPUT into TARGET), Command Flash (drill decks generated fromcommands.yaml/tutorials.yaml+ curated decks). Scores persist; wins feed story XP. - Reference mode + concept spotlight (
web/assets/js/reference.js): searchable cheatsheet library from the command registries, a story-sidebar card showing what the current room teaches, and inline syntax hints while typing. - Bash-emulator kernel upgrades (
web/assets/js/runtime.js): output redirection (>,>>), glob expansion (*,?, quote-aware), multi-file +-r/-l/-n/-c/-wand real-regexgrep,sort -u,uniq -c/-d, and newcut,tr,sed,nl,revcommands. - Arcade content registry (
src/help/data/arcade.yaml) exported toweb/data/arcade.jsonbyexport_static_web.py(validated byvalidate_static_web.py). - Lean playtest harness (
src/playtest/): FastMCP server (bashcrawl_start/observe/command/report_gap) driving a persistent PTY bash session in a sandbox copy of the dungeon; JSONL recorder; scorer with rooms/scrolls gates. Smoke-tested bytest/integration/test_mcp_server.py.
Removed¶
- Python Textual TUI + old MCP/agent harness (
src/terminal-illness/), Flask log viewer (src/viewer/),screenshots/(167 SVGs),main.shlauncher, Docker/devcontainer tooling,.archives/, and the launcher-onlylib/scripts (emulator,state,ui,quests,room_loader, …).lib/retainscolors.sh,log.sh,yaml_reader.sh,reset.sh. - TUI/agent/viewer test suites (
test/ai/,test/demo/,test/analysis/, and theti.*unit/integration tests) and their CI workflows (test-framework,code-quality,release).
Changed¶
- CI/CD redesigned (5 workflows → 3):
ci.ymlis now the single PR/push gate with three jobs — consolidatedlint(one runner for shellcheck/yamllint/ markdownlint/ruff),test(the full pytest suite now runs in CI, closing a gap where 175 tests ran nowhere, plusmake validate-contractsand junit artifacts), andmacos-smoke(real gameplay on stock macOS bash 3.2, replacing the Windows matrix the unix-only suite couldn't run).game-tests.ymldeleted — its unique checks (shebangs, boilerplate, unlock targets, main-path scroll depth) moved intovalidate_content_contracts.pyso they run locally too.dependency-update.ymldeleted — redundant with Dependabot, and itspip freezerewrite would have destroyed the commented requirements files.pages.ymlgains pip caching and usesmake web-test;blank-slate-audit.ymlnow writes the scorer verdict to the run summary.make validate-contractsadditionally runs the content-index check. - Terminal play is launcher-free:
./setup.sh && cd entrance && cat scroll. - Python deps reduced to
pyyaml+mcp(dev:pytest,pytest-timeout,jsonschema,ruff). validate_runtime_commands.pyvalidates the single remaining runtime (the web emulator) againstruntime_commands.yaml.scripts/playtest.shtargetsplaytest.mcp_serverand gates on median rooms-visited / scrolls-read (BLANK_SLATE_GATE_ROOMS/_SCROLLS).- Makefile, CI workflows,
.mcp.json,.vscode/, docs, README, and CLAUDE.md rewritten for the two-surface architecture.
[Unreleased] - 2026-03-04¶
Added¶
- Walkthrough-driven test model (
test/datasets/walkthrough.json,test/fixtures/walkthrough.py) - Introduced a single source of truth for rooms, encounters, and progression steps
-
Added new integration suites for classic walkthrough coverage and encounter validation
-
Viewer walkthrough/templates expansion (
src/viewer/templates/walkthrough/,src/viewer/templates/tests/) - Added new test/walkthrough-facing template pages and related routing support
Changed¶
- Integration test structure refresh (
test/integration/) - Replaced older
test_game_scripts.py,test_game_progression.py, andtest_combat.pywith consolidated walkthrough/encounter-focused suites -
Updated test fixtures and log/screenshot plumbing to align with walkthrough manifests
-
Help and room metadata updates (
src/help/bashcrawl_help.sh,src/help/data/rooms.yaml) -
Expanded room/help data wiring to match the updated walkthrough-driven model
-
Viewer analytics/session processing updates (
src/viewer/) - Updated screenshot/session loaders, analytics engine routes, and gallery/dashboard templates
Fixed¶
- Interactive executable stdin handling in TUI engine (
src/terminal-illness/ti/filesystem.py) -
run_script()now accepts optionalstdin_inputand defaults to multiple responses, preventing EOF on scripts with multiplereadprompts (for example statue/monster/ghost) -
Encounter tests for multi-prompt scripts (
test/integration/test_encounters.py,test/integration/test_native_walkthrough.py) - Updated scripted stdin payloads so y/n + follow-up prompts are consumed correctly
- Added assertions for expected statue reward output when sword path is taken
[Unreleased] - 2026-03-03¶
Added¶
- Offline TUI screenshot test (
test/ai/test_offline_screenshots.py) - Integration test (
@pytest.mark.integration) that replays a scripted command sequence through the real Textual TUI agent - Generates SVG screenshots and JSONL session logs without requiring
ANTHROPIC_API_KEY -
Accessible through the Flask viewer for visual regression review
-
AI-driven TUI screenshot test (
test/ai/test_agent_screenshots.py) - Two-phase approach: Claude (
TestAgent) +NonInteractiveEnginegenerates a command sequence, then replays throughti.agentsubprocess to capture real SVG screenshots -
Marked
@pytest.mark.ai(requiresANTHROPIC_API_KEY); skipped in default CI run -
Lightweight SVG renderer fixture (
test/fixtures/svg_renderer.py) - Renders game state (command, output, location, inventory, HP) as dark-terminal SVGs
-
Used by offline screenshot tests as a lightweight alternative to the full Textual TUI
-
.env.example— template documentingANTHROPIC_API_KEYand other required environment variables
Changed¶
-
test/conftest.py: Automatically loads.envfrom repo root viapython-dotenv;ANTHROPIC_API_KEYis now picked up without manualexport -
test/requirements.txt: Addedpython-dotenv>=1.0dependency
Fixed¶
-
test/ai/session_runner.py:_cmd_letresult now correctly wrapped in aCommandResultobject; previously the raw tuple was returned, causing downstream attribute errors -
main.shline 865: Added# shellcheck disable=SC2163—export -f "$func_name"intentionally exports the function named by the variable, not the variable itself -
lib/native_state.sh: Added# shellcheck disable=SC2317—exit 1afterreturn 1 2>/dev/nullis reachable when the file is executed outside a function context
[Unreleased] - 2026-02-24¶
Changed¶
- Refactored CI workflows (
.github/workflows/) - Consolidated ShellCheck, yamllint, and markdownlint into single
ci.yml— removed duplicates fromcode-quality.ymlandgame-tests.yml - Removed redundant Python sanity-check job from
ci.yml(covered bytest-framework.yml) - Removed
lint-and-formatjob fromcode-quality.yml(now only CodeQL security scan + quality metrics) - Removed inline ShellCheck step from
game-tests.yml(centralized inci.yml) - Dropped
masterbranch references across all workflows (repo only usesmain) - Upgraded
dependency-update.ymlPython version from 3.11 to 3.12 dependency-update.ymlnow updates bothtest/requirements.txtandsrc/terminal-illness/requirements.txt- Replaced inline
pip install yamllintwithibiqlik/action-yamllint@v3action inci.yml - Cleaned trailing whitespace in
release.yml
Fixed¶
- CI failures across all workflows
- Fixed
dependency-update.ymlbroken YAML: malformedbodyblock with spuriousbranch:key mid-string - Fixed
test-framework.ymlsetup crash: addedTERM: dumbenv and--quickflag tosetup.shcalls (preventsclearcommand failure in headless CI runners) - Added 10 ShellCheck disable rules to
.shellcheckrc(SC2155, SC2126, SC2010, SC2012, SC2143, SC2043, SC2046, SC2181, SC2295, SC2329) — all style/info-level warnings in helper scripts that were blocking CI - Updated
.yamllint.yml: addedtruthy: check-keys: false(for GH Actionson:key),indentation: indent-sequences: whatever(for standard GH Actions formatting) - Excluded
.venv/from ShellCheck file discovery inci.yml
[Unreleased] - 2026-02-23¶
Added¶
- Bashcrawl Observatory viewer documentation (
docs/viewer.md,src/viewer/README.md) - Full reference for the Flask log viewer: installation, all pages, JSON API, configuration, troubleshooting
docs/log-viewer-reference.md: Quick-reference card for API endpoints and configuration keys-
Replaced the design-intent
docs/log-viewer-plan.mdwith accurate operational docs -
AI agent test documentation (
docs/getting-started.md,docs/advanced.md) - Step-by-step guide for running agent-mode playtests and interpreting screenshots
-
Advanced section covering test suite markers (
unit,integration,ai,demo) andANTHROPIC_API_KEYsetup -
no-screenshot.svgplaceholder (src/viewer/static/img/no-screenshot.svg) - Dark-background SVG placeholder shown in gallery/index when an image fails to load
Fixed¶
- Screenshot gallery rendering (
src/viewer/loaders/screenshots.py, gallery/index templates) _abs_to_rel()converts absolute manifest paths to URL-relative paths using.resolve()_load_manifest()parsesmanifest.jsonand emitsScreenshotobjects with correct paths_parse_screenshot_dir()rewritten with 3-strategy fallback: top-level manifest → subdir manifest →rglob("*.svg")- Stub SVG filter: files < 200 bytes (test fixture stubs) excluded; sessions with only stubs skipped
ScreenshotStore.load_all()passesscreenshots_dirto_parse_screenshot_dirfor path resolution-
Templates updated: all image
srcattributes now usesc.path/sc.screenshots[0].pathinstead of broken string concatenation -
Room visit statistics (
src/viewer/loaders/sessions.py) -
Sessions loader now extracts
roomfromcommandevents (real data format) rather than looking forroom_enterevents that didn't exist; rooms went from 0 → 39 entries across 11 sessions -
Feedback page content (
src/viewer/loaders/feedback.py) - Fixed metrics regex from
(.+?)to([^|\n]+?)to prevent matching across pipe|and newline boundaries -
Added
FeedbackStore.refresh()method for reloading feedback files without restarting the server -
Analytics/feedback API refresh (
src/viewer/routes/api.py) - Added
POST /api/analytics/refreshendpoint (callsstore.refresh()+engine.invalidate_cache()) -
Added
POST /api/feedback/refreshendpoint (callsFeedbackStore.refresh()) -
ls -Fcrash in TUI terminal engine (src/terminal-illness/ti/terminal_engine.py) _cmd_ls(args)now separates flags (strings starting with-) from optional path argument- Flag chars collected into a set;
show_hidden = "a" in flagspassed tofs.ls() -
Fixes
NotADirectoryError: Not a directory: -Fthat appeared in agent session screenshots -
Hidden files in TUI filesystem (
src/terminal-illness/ti/filesystem.py) fs.ls(cwd, path="", show_hidden=False)— newshow_hiddenparameter-
Dotfiles (
.chapel/,.vault/,.rift/,.scrap/) hidden by default; shown withls -a/ls -la/ls -aF -
Merlin AI guide visibility (
src/terminal-illness/ti/chat_context.py) -
GameContextBuilder.build()now callsfs.ls(cwd, "", show_hidden=True)so Merlin can see and mention hidden rooms when guiding players -
Agent screenshot path nesting (
src/terminal-illness/ti/agent.py) - Removed the extra
<timestamp>_agent_session/subdirectory layer; all SVGs andmanifest.jsonnow written directly to--screenshot-dir - Fixes 15 previously failing integration tests in
TestAgentScreenshotsandTestTuiCriticalPath/UIElements/EdgeCaseswhere tests expected flatshot_dir/<name>.svgbut agent wrote to a nested subdir - Viewer screenshot loader strategy-1 (top-level
manifest.json) now resolves immediately for all new sessions
Changed¶
docs/index.md: Added viewer and AI agent test links in the navigation sectiondocs/getting-started.md: Added Observatory viewer quick-start and AI agent test sectiondocs/advanced.md: Expanded with full Observatory viewer and AI test workflow sectionsdocs/log-viewer-plan.md: Replaced bydocs/viewer.mdanddocs/log-viewer-reference.md(deleted)
Added¶
- Merlin AI Chat Guide (
src/terminal-illness/ti/) ai_chat.py:AIChatService— wraps Anthropic Claude (claude-sonnet-4-20250514) with async streaming, rolling 20-turn conversation history, proactive nudge strings, and static fallback whenANTHROPIC_API_KEYis absentchat_context.py:GameContextBuilder— assembles game context snapshots (room, inventory, HP, quest, recent commands, scroll excerpt) for prompt injection;detect_stuck()identifies repetitive command patterns (threshold: 3)chat_panel.py: TextualChatPanelwidget — toggleable right-side panel with streaming token display, proactive nudges, and styled user/Merlin bubbleschat_cli.py: CLI bridge — letsbash help.sh merlininvoke Claude as a standalone Python process-
data/merlin_prompt.txt: Merlin wizard persona, Socratic teaching rules, and context placeholder template (7 injected fields) -
F3 Key Binding in Textual TUI (
src/terminal-illness/ti/app.py) - Toggles Merlin chat panel open/closed without leaving the game terminal
merlin <question>command opens panel and submits question in one step- Proactive nudges fired on: room change, HP < 20, quest complete, stuck detection
-
@work(thread=True, exit_on_error=False)— API errors display in chat panel instead of closing the session; fulltry/exceptwithself.log.error()fallback -
help merlin/help asksubcommands (src/help/bashcrawl_help.sh,src/help.sh) bash help.sh merlin "<question>"calls the Python AI bridge for terminal-only players-
Falls back to existing
ai_engine.shstatic hints when Python or API key unavailable -
VS Code Debug Configurations (
.vscode/launch.json) 🧙 Debug TUI — Merlin Chat: Full Textual game withTEXTUAL_LOG=/tmp/textual_debug.log🧙 Debug Merlin CLI: Single-turn CLI bridge for step-through debugging🧙 Debug AI Chat Tests: pytest runner fortest/unit/test_ai_chat.py🧙 Debug TUI — Break on Exception:justMyCode: falsefor worker-thread crashes-
🧪 Debug All Unit Tests: Full unit suite runner -
Unit Tests (
test/unit/test_ai_chat.py) — 28 tests covering: GameContextBuilder.build()anddetect_stuck()(6 cases)GameContext.as_dict()placeholder formattingAIChatServiceAPI availability detection, fallback responses, nudge strings, history trimming, system prompt rendering, Claude mock integration
Added¶
- Log Viewer Flask Web App (
src/viewer/) - Full Flask web application for browsing JSONL session logs and analytics
- Routes: session list/detail, feedback list/detail, screenshots gallery, dungeon map, analytics dashboard, live agent view
- Live agent monitoring page (
/live/agent) with real-time event stream via SSE - REST API endpoints (
/api/) for sessions, screenshots, feedback, and live agent status - Static assets: themed CSS (
theme.css,components.css,gallery.css,map.css), JavaScript modules for map rendering, gallery, analytics, and live agent feed - Configurable via
src/viewer/config.py; launchable withpython3 -m src.viewer --port 5000 -
See docs/log-viewer-plan.md for design rationale
-
Live Agent Session Logging (
test/ai/live_logger.py) - Real-time JSONL logger that writes agent events during AI test runs to
logs/live_agent.jsonl -
Feeds the live agent monitoring page in the log viewer
-
Hidden Study Room (
entrance/.chapel/courtyard/aviary/hall/library/.study/) - New deeply-nested hidden area with
scroll(teaches advanced grep/search) andgrimoireencounter -
Unlocked after completing the library section
-
Log Cleanup Utility (
lib/clean_logs.sh) - New utility script to prune old session logs and screenshots by age or count
-
Dry-run mode (
--dry) for safe preview before deletion -
Help System Scroll (
src/help/scroll) -
Standalone scroll for the help system directory, documenting available help commands
-
Workflow Prompts (
.github/prompts/) -
test-doc-commit-push.prompt.md: Complete release pipeline prompt for Copilot -
tomegame object (entrance/.chapel/courtyard/aviary/hall/library/tome) -
New interactive encounter in the library teaching advanced text processing
-
Expanded help data (
src/help/data/) map.yaml: Additional dungeon areas and connectionsquests.yaml: 3 new quest definitions for advanced areas-
rooms.yaml: 45 lines of new room metadata for hidden areas -
AI test infrastructure improvements (
test/ai/) agent.py: 144 lines added — enhancedTestAgentwith live logging, retry logic, and structured outputsession_runner.py: 86 lines added — session management with log capture and timing metrics-
All AI test files updated to use improved agent and session runner APIs
-
Walkthrough documentation (docs/walkthrough.md)
-
Complete step-by-step walkthrough with SVG screenshot references for every command
-
Agent Mode for AI Assistants (
--agentflag) - Headless Textual TUI mode driven via stdin/stdout protocol
- SVG screenshot capture after every command using Textual's
save_screenshot() SCREENSHOT,STATUS, andEXITmeta-commandsREADY>sentinel for synchronization with programmatic callers--screenshot-dirflag to control screenshot output location-
See docs/agent-protocol.md for full specification
-
Bash Agent REPL (
--agent-bashflag) - Lightweight bash-only agent mode (no Python dependency)
- Same
READY>/CMD>protocol for consistent integration -
Automatic fallback when Python/Textual is unavailable
-
TerminalEngine programmatic API
execute(cmd_line)method for dispatching commands without interactive REPLget_completions(text)method for programmatic tab completionoutput_callbackparameter to route output to Textual widgets-
on_quest_completecallback for quest event handling -
Agent playtest report (docs/agent-playtest-report.md)
- Comprehensive playtest findings from AI-agent-driven session
- 15 recommendations across content, architecture, TUI, and testing
-
Priority matrix for implementation ordering
-
Agent protocol documentation (docs/agent-protocol.md)
- Full stdin/stdout protocol specification
- CLI flag reference for both
main.shandpython3 -m ti.agent -
Integration guide for AI assistants
-
Test suite for agent mode (
test/unit/test_agent.py,test/integration/test_agent_mode.py) - Unit tests for TerminalEngine
execute(),get_completions(), and constructor - Integration tests for the full agent pipeline via subprocess
- Screenshot generation and SVG validation tests
Fixed¶
textualdependency added totest/requirements.txt— unit tests forBashcrawlAppagent mode previously failed withModuleNotFoundError: No module named 'textual'lib/reset.shexpanded to clean additional stray files from TUI sessions and new game areaslogs/screenshots/.gitkeepremoved — screenshots directory is now created dynamically- TerminalEngine constructor now accepts
output_callbackandon_quest_completeparameters thatBashcrawlAppwas already passing (previously causedTypeError) - BashcrawlApp modal screens (Welcome/Load) no longer block headless operation
when
agent_mode=True action_submit()async handling — properly awaited in Textual 8.0+ to avoidRuntimeWarning: coroutine never awaited
Changed¶
src/terminal-illness/ti/agent.py— enhanced with improved output handling and protocol robustnesssrc/terminal-illness/ti/filesystem.py— minor fixes for cross-platform path handlingsrc/terminal-illness/ti/terminal_engine.py— 18 lines updated for improved command dispatchdocs/agent-protocol.md— expanded with additional protocol details and exampleslogs/README.md— restructured and simplified documentation for the logs directory.github/copilot-instructions.md— updated with viewer, live logger, and hidden area documentationsetup.sh— minor fix for macOS compatibility (sed -i.bakpath handling)main.shhelp text updated with agent mode flags and examplesmain.shargument parser extended to handle--agent,--agent-bash,--screenshot-dirflags- All SVG screenshots in
screenshots/regenerated with updated TUI rendering