# AGENTS.md Guidance for coding agents working in this repository. ## Mission Build `left4me` according to the two implementation plans: - `docs/superpowers/plans/2026-04-22-l4d2-host-lib-v1.md` - `docs/superpowers/plans/2026-04-23-l4d2-web-app-v1.md` Do not invent architecture outside these plans unless explicitly requested. ## Current Project State - `l4d2host/` and `l4d2web/` implementation directories exist. - Implementation plans remain the source of truth for contract changes and task sequencing. ## Non-Negotiable Constraints ### Workspace and tools - Do not use git worktrees. - Repo is a uv workspace; Python is pinned to 3.13 via `.python-version`. After fresh checkout: install `uv` (`brew install uv` / `curl -LsSf https://astral.sh/uv/install.sh | sh`), then `direnv allow` (or `uv sync` directly). See README **Local development** for details. ### Dev server and filesystem paths - **Production paths (`/var/lib/left4me`, `/usr/local/lib/systemd/system`, `/usr/local/libexec/left4me`, `/etc/left4me`) exist only on Linux deploy hosts.** Never create or write to these on a developer machine. They are referenced in `l4d2host/l4d2host/paths.py` and the spec only as the production layout. - **For local dev, always use `scripts/dev-server.py`.** It sets `LEFT4ME_ROOT=./.tmp/dev-server`, runs migrations, seeds demo content (admin + blueprint + script overlay + files overlay), and starts Flask on port 5051. Reset state with `rm -rf .tmp/dev-server` then re-run. Never invoke `flask run` directly — that leaves `LEFT4ME_ROOT` unset and the app falls back to the production `/var/lib/left4me`, which on macOS surfaces as "route returns 404 / empty modal / file not found" and can be mistaken for a code bug. - **All ephemeral dev state lives under `.tmp/`** (gitignored). Use `$TMPDIR` only for transient files outside the repo. Do NOT use `/tmp`, `~/Library/Application Support`, or any system path for project state — only `.tmp/` (project-local) or `$TMPDIR` (sandbox-blessed). - **Symptom-to-cause translation:** if a route returns 404 or behaves as if the filesystem is empty, the first diagnosis is "`LEFT4ME_ROOT` is wrong" (defaulted to the production path), not "code bug." Restart via `scripts/dev-server.py`. ### Planning artifacts - Design specs live in `docs/superpowers/specs/` as `YYYY-MM-DD--design.md`. - Implementation plans live in `docs/superpowers/plans/` as `YYYY-MM-DD-.md` (suffix the topic with `-v1`/`-v2`/etc. if a plan is versioned). - Commit both to git as soon as the user approves them. - Do not leave specs or plans outside this repo. The `~/.claude/plans/.md` plan-mode scratch file is acceptable while plan mode is open; the persisted artifact must end up under `docs/superpowers/` and be committed. ### Naming and boundaries - Use `l4d2` naming consistently. - Keep host library and web app as separate components. - Do not collapse them into one package. ### Host library (`l4d2host` / `l4d2ctl`) - Exposed CLI write command set is fixed: - `install` - `initialize -f ` - `start ` - `stop ` - `delete ` - CLI read commands are allowed for web/host boundary consistency: - `status --json` - `logs --lines --follow/--no-follow` - Runtime paths are rooted at `LEFT4ME_ROOT`, defaulting to `/var/lib/left4me`. - Deployment/config management owns global units under `/usr/local/lib/systemd/system` and privileged helpers under `/usr/local/libexec/left4me`. - Overlay directories are populated by the web app (workshop downloads, managed-global refresh). The host library only mounts them. - Fail-fast subprocess behavior; pass raw stderr; propagate return code. - No lock manager, no rollback, no preflight runtime checks. - Delete missing instance/runtime dirs must succeed (no-op). - Read APIs required for web app integration: - `get_instance_status(name)` - `stream_instance_logs(name, lines=200, follow=True)` ### Web app (`l4d2-web-app`) - Flask + server-rendered templates + vendored HTMX. - No external frontend framework/dependencies. - Custom CSS with tokenized, consistent link and accent colors. - Local username/password auth and `admin` flag. - Persist command logs in `job_logs` table (retain indefinitely). - Desired vs actual server state model. - Live logs in UI for both jobs and servers. - Web app host operations go through `l4d2ctl` via a host command client, not direct `l4d2host` imports. - Blueprint semantics (locked): - private per user in v1 - live-linked to servers - no server-level overrides - deleting in-use blueprint is blocked - updates apply on next action - servers can reassign blueprint anytime ## Delivery Workflow 1. Read both plan files fully before coding. 2. Execute plan tasks in order. 3. Keep changes scoped to one task at a time. 4. Run task-level tests before moving forward. 5. Do not claim completion without command evidence. 6. Keep docs updated when behavior/contracts change. ## Verification Expectations Before claiming success on any step, run the relevant command and report actual output status. Typical commands (once components exist): - `pytest l4d2host/tests -q` - `pytest l4d2web/tests -q` ## Change Control - If a requested change conflicts with this file, follow explicit user instruction. - If plans and code diverge, update plans or flag the mismatch clearly. ## End-to-end tests The Playwright-based browser tests under `l4d2web/tests/e2e/` need a chromium binary, fetched on first setup: ```bash uv run playwright install chromium ``` Always invoke as `uv run pytest -m e2e ...` (excluded from the default fast suite via the `e2e` marker). Other forms crash Chromium under the macOS sandbox; only this exact invocation is exempt. ## Editor bundle (CodeMirror 6) The in-browser code editor on the blueprint config / overlay script / files-modal textareas is bundled from `l4d2web/scripts/editor-src/` via esbuild and committed pre-built to `l4d2web/l4d2web/static/vendor/editor.bundle.js`. Source lives under `l4d2web/scripts/editor-src/`; design and plan at `docs/superpowers/specs/2026-05-17-textarea-editor-v2-design.md` and `docs/superpowers/plans/2026-05-17-textarea-editor-v2.md`. Rebuild after editing the source: ```bash ./l4d2web/scripts/build-editor.sh ``` Requires `node` + `npm` locally. The script overrides the npm cache to `$TMPDIR/npm-cache` (set `NPM_CACHE` to override) to dodge root-owned files in `~/.npm/_cacache/` from older npm versions. Commit the regenerated `editor.bundle.js`, `editor.bundle.css`, and `editor.bundle.sha256` alongside any source change. Regenerate the autocomplete vocab from `./cvar_list` (live L4D2 cvarlist dump committed at repo root) after replacing the dump: ```bash ./l4d2web/scripts/build-vocab.py ```