From 288eda7c37efdb116f71bfa8762b4956ba800e44 Mon Sep 17 00:00:00 2001 From: mwiegand Date: Tue, 5 May 2026 23:47:06 +0200 Subject: [PATCH] chore(l4d2): flatten component layout --- .gitignore | 5 + AGENTS.md | 4 +- README.md | 8 +- .../plans/2026-04-22-l4d2-host-lib-v1.md | 140 +++++------ .../plans/2026-04-23-l4d2-web-app-v1.md | 232 +++++++++--------- .../plans/2026-05-05-l4d2-host-smoke-test.md | 8 +- .../2026-05-05-l4d2-host-smoke-test-design.md | 6 +- .../l4d2-host-lib => l4d2host}/README.md | 0 .../src/l4d2host => l4d2host}/__init__.py | 0 .../src/l4d2host => l4d2host}/cli.py | 0 .../src/l4d2host => l4d2host}/fs/__init__.py | 0 .../src/l4d2host => l4d2host}/fs/base.py | 0 .../fs/fuse_overlayfs.py | 0 .../src/l4d2host => l4d2host}/instances.py | 0 .../src/l4d2host => l4d2host}/logs.py | 0 .../src/l4d2host => l4d2host}/process.py | 0 .../l4d2-host-lib => l4d2host}/pyproject.toml | 9 +- .../src/l4d2host => l4d2host}/spec.py | 0 .../src/l4d2host => l4d2host}/status.py | 0 .../l4d2host => l4d2host}/steam_install.py | 0 .../src/l4d2host => l4d2host}/systemd_user.py | 0 .../templates/__init__.py | 0 .../templates/l4d2@.service | 0 .../tests/test_cli.py | 0 .../tests/test_initialize.py | 0 .../tests/test_install.py | 0 .../tests/test_lifecycle.py | 0 .../tests/test_logs.py | 0 .../tests/test_process.py | 0 .../tests/test_spec.py | 0 .../tests/test_status.py | 0 .../l4d2-web-app => l4d2web}/README.md | 0 .../src/l4d2web => l4d2web}/__init__.py | 0 .../l4d2-web-app => l4d2web}/alembic.ini | 0 .../l4d2-web-app => l4d2web}/alembic/env.py | 0 .../alembic/versions/0001_initial.py | 0 .../src/l4d2web => l4d2web}/app.py | 0 .../src/l4d2web => l4d2web}/auth.py | 0 .../src/l4d2web => l4d2web}/cli.py | 0 .../src/l4d2web => l4d2web}/config.py | 0 .../src/l4d2web => l4d2web}/db.py | 0 .../src/l4d2web => l4d2web}/models.py | 0 .../l4d2-web-app => l4d2web}/pyproject.toml | 14 +- .../l4d2web => l4d2web}/routes/__init__.py | 0 .../l4d2web => l4d2web}/routes/auth_routes.py | 0 .../routes/blueprint_routes.py | 0 .../l4d2web => l4d2web}/routes/job_routes.py | 0 .../l4d2web => l4d2web}/routes/log_routes.py | 0 .../routes/overlay_routes.py | 0 .../l4d2web => l4d2web}/routes/page_routes.py | 0 .../routes/server_routes.py | 0 .../l4d2web => l4d2web}/services/__init__.py | 0 .../services/job_worker.py | 0 .../services/l4d2_facade.py | 0 .../l4d2web => l4d2web}/services/security.py | 0 .../l4d2web => l4d2web}/services/spec_yaml.py | 0 .../l4d2web => l4d2web}/services/status.py | 0 .../static/css/components.css | 0 .../l4d2web => l4d2web}/static/css/layout.css | 0 .../l4d2web => l4d2web}/static/css/logs.css | 0 .../l4d2web => l4d2web}/static/css/tokens.css | 0 .../src/l4d2web => l4d2web}/static/js/csrf.js | 0 .../src/l4d2web => l4d2web}/static/js/sse.js | 0 .../static/vendor/htmx.min.js | 0 .../templates/admin_overlays.html | 0 .../l4d2web => l4d2web}/templates/base.html | 0 .../templates/blueprints.html | 0 .../templates/dashboard.html | 0 .../templates/server_detail.html | 0 .../tests/test_auth.py | 0 .../tests/test_blueprints.py | 0 .../tests/test_health.py | 0 .../tests/test_job_logs.py | 0 .../tests/test_job_worker.py | 0 .../tests/test_l4d2_facade.py | 0 .../tests/test_models.py | 0 .../tests/test_overlays.py | 0 .../tests/test_pages.py | 0 .../tests/test_security.py | 0 .../tests/test_servers.py | 0 .../tests/test_status_and_server_logs.py | 0 81 files changed, 221 insertions(+), 205 deletions(-) rename {components/l4d2-host-lib => l4d2host}/README.md (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/__init__.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/cli.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/fs/__init__.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/fs/base.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/fs/fuse_overlayfs.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/instances.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/logs.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/process.py (100%) rename {components/l4d2-host-lib => l4d2host}/pyproject.toml (66%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/spec.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/status.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/steam_install.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/systemd_user.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/templates/__init__.py (100%) rename {components/l4d2-host-lib/src/l4d2host => l4d2host}/templates/l4d2@.service (100%) rename {components/l4d2-host-lib => l4d2host}/tests/test_cli.py (100%) rename {components/l4d2-host-lib => l4d2host}/tests/test_initialize.py (100%) rename {components/l4d2-host-lib => l4d2host}/tests/test_install.py (100%) rename {components/l4d2-host-lib => l4d2host}/tests/test_lifecycle.py (100%) rename {components/l4d2-host-lib => l4d2host}/tests/test_logs.py (100%) rename {components/l4d2-host-lib => l4d2host}/tests/test_process.py (100%) rename {components/l4d2-host-lib => l4d2host}/tests/test_spec.py (100%) rename {components/l4d2-host-lib => l4d2host}/tests/test_status.py (100%) rename {components/l4d2-web-app => l4d2web}/README.md (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/__init__.py (100%) rename {components/l4d2-web-app => l4d2web}/alembic.ini (100%) rename {components/l4d2-web-app => l4d2web}/alembic/env.py (100%) rename {components/l4d2-web-app => l4d2web}/alembic/versions/0001_initial.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/app.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/auth.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/cli.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/config.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/db.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/models.py (100%) rename {components/l4d2-web-app => l4d2web}/pyproject.toml (57%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/routes/__init__.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/routes/auth_routes.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/routes/blueprint_routes.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/routes/job_routes.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/routes/log_routes.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/routes/overlay_routes.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/routes/page_routes.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/routes/server_routes.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/services/__init__.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/services/job_worker.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/services/l4d2_facade.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/services/security.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/services/spec_yaml.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/services/status.py (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/static/css/components.css (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/static/css/layout.css (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/static/css/logs.css (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/static/css/tokens.css (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/static/js/csrf.js (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/static/js/sse.js (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/static/vendor/htmx.min.js (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/templates/admin_overlays.html (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/templates/base.html (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/templates/blueprints.html (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/templates/dashboard.html (100%) rename {components/l4d2-web-app/src/l4d2web => l4d2web}/templates/server_detail.html (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_auth.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_blueprints.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_health.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_job_logs.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_job_worker.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_l4d2_facade.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_models.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_overlays.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_pages.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_security.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_servers.py (100%) rename {components/l4d2-web-app => l4d2web}/tests/test_status_and_server_logs.py (100%) diff --git a/.gitignore b/.gitignore index 91fda78..2b7c17a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ .worktrees/ +.venv/ +.pytest_cache/ +__pycache__/ +*.pyc +*.egg-info/ l4d2web.db* diff --git a/AGENTS.md b/AGENTS.md index 74f670e..be8c070 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,8 +74,8 @@ Before claiming success on any step, run the relevant command and report actual Typical commands (once components exist): -- `pytest components/l4d2-host-lib/tests -q` -- `pytest components/l4d2-web-app/tests -q` +- `pytest l4d2host/tests -q` +- `pytest l4d2web/tests -q` ## Change Control diff --git a/README.md b/README.md index 80c4bd6..037cdf0 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ Implementation plans are the source of truth: ## Planned Repository Layout -- `components/l4d2-host-lib/` -- `components/l4d2-web-app/` +- `l4d2host/` +- `l4d2web/` - `docs/superpowers/plans/` ## Tech Stack (planned) @@ -52,8 +52,8 @@ Implementation plans are the source of truth: ## Recommended Implementation Order -1. Implement `components/l4d2-host-lib` plan first. -2. Implement `components/l4d2-web-app` plan second. +1. Implement `l4d2host` plan first. +2. Implement `l4d2web` plan second. 3. Keep tests green task-by-task (TDD flow from plans). 4. Keep commits small and aligned with plan tasks. diff --git a/docs/superpowers/plans/2026-04-22-l4d2-host-lib-v1.md b/docs/superpowers/plans/2026-04-22-l4d2-host-lib-v1.md index 74666c6..d1b09bb 100644 --- a/docs/superpowers/plans/2026-04-22-l4d2-host-lib-v1.md +++ b/docs/superpowers/plans/2026-04-22-l4d2-host-lib-v1.md @@ -39,35 +39,35 @@ ## Planned File Layout -- `components/l4d2-host-lib/pyproject.toml` -- `components/l4d2-host-lib/src/l4d2host/cli.py` -- `components/l4d2-host-lib/src/l4d2host/spec.py` -- `components/l4d2-host-lib/src/l4d2host/process.py` -- `components/l4d2-host-lib/src/l4d2host/steam_install.py` -- `components/l4d2-host-lib/src/l4d2host/systemd_user.py` -- `components/l4d2-host-lib/src/l4d2host/fs/base.py` -- `components/l4d2-host-lib/src/l4d2host/fs/fuse_overlayfs.py` -- `components/l4d2-host-lib/src/l4d2host/instances.py` -- `components/l4d2-host-lib/src/l4d2host/status.py` -- `components/l4d2-host-lib/src/l4d2host/logs.py` -- `components/l4d2-host-lib/src/l4d2host/templates/l4d2@.service` -- `components/l4d2-host-lib/tests/test_cli.py` -- `components/l4d2-host-lib/tests/test_spec.py` -- `components/l4d2-host-lib/tests/test_process.py` -- `components/l4d2-host-lib/tests/test_install.py` -- `components/l4d2-host-lib/tests/test_initialize.py` -- `components/l4d2-host-lib/tests/test_lifecycle.py` -- `components/l4d2-host-lib/tests/test_status.py` -- `components/l4d2-host-lib/tests/test_logs.py` -- `components/l4d2-host-lib/README.md` +- `l4d2host/pyproject.toml` +- `l4d2host/cli.py` +- `l4d2host/spec.py` +- `l4d2host/process.py` +- `l4d2host/steam_install.py` +- `l4d2host/systemd_user.py` +- `l4d2host/fs/base.py` +- `l4d2host/fs/fuse_overlayfs.py` +- `l4d2host/instances.py` +- `l4d2host/status.py` +- `l4d2host/logs.py` +- `l4d2host/templates/l4d2@.service` +- `l4d2host/tests/test_cli.py` +- `l4d2host/tests/test_spec.py` +- `l4d2host/tests/test_process.py` +- `l4d2host/tests/test_install.py` +- `l4d2host/tests/test_initialize.py` +- `l4d2host/tests/test_lifecycle.py` +- `l4d2host/tests/test_status.py` +- `l4d2host/tests/test_logs.py` +- `l4d2host/README.md` ### Task 1: Scaffold package and CLI entrypoint **Files:** -- Create: `components/l4d2-host-lib/pyproject.toml` -- Create: `components/l4d2-host-lib/src/l4d2host/__init__.py` -- Create: `components/l4d2-host-lib/src/l4d2host/cli.py` -- Test: `components/l4d2-host-lib/tests/test_cli.py` +- Create: `l4d2host/pyproject.toml` +- Create: `l4d2host/__init__.py` +- Create: `l4d2host/cli.py` +- Test: `l4d2host/tests/test_cli.py` - [ ] **Step 1: Write failing CLI help test** @@ -85,7 +85,7 @@ def test_help_lists_v1_commands(): - [ ] **Step 2: Run test and verify failure** -Run: `pytest components/l4d2-host-lib/tests/test_cli.py -q` +Run: `pytest l4d2host/tests/test_cli.py -q` Expected: FAIL with missing package/app. - [ ] **Step 3: Implement pyproject script and minimal CLI app** @@ -132,21 +132,21 @@ def delete(name: str) -> None: - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-host-lib/tests/test_cli.py -q` +Run: `pytest l4d2host/tests/test_cli.py -q` Expected: PASS. - [ ] **Step 5: Commit scaffold** ```bash -git add components/l4d2-host-lib +git add l4d2host git commit -m "feat(l4d2): scaffold package and v1 CLI entrypoint" ``` ### Task 2: Implement YAML spec parser **Files:** -- Create: `components/l4d2-host-lib/src/l4d2host/spec.py` -- Test: `components/l4d2-host-lib/tests/test_spec.py` +- Create: `l4d2host/spec.py` +- Test: `l4d2host/tests/test_spec.py` - [ ] **Step 1: Write failing parser tests** @@ -189,7 +189,7 @@ def test_unknown_keys_ignored(tmp_path: Path): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-host-lib/tests/test_spec.py -q` +Run: `pytest l4d2host/tests/test_spec.py -q` Expected: FAIL. - [ ] **Step 3: Implement parser and dataclass** @@ -220,21 +220,21 @@ def load_spec(path: Path) -> InstanceSpec: - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-host-lib/tests/test_spec.py -q` +Run: `pytest l4d2host/tests/test_spec.py -q` Expected: PASS. - [ ] **Step 5: Commit parser** ```bash -git add components/l4d2-host-lib/src/l4d2host/spec.py components/l4d2-host-lib/tests/test_spec.py +git add l4d2host/spec.py l4d2host/tests/test_spec.py git commit -m "feat(l4d2): add spec parser with required port and permissive fields" ``` ### Task 3: Build streaming process runner with callbacks **Files:** -- Create: `components/l4d2-host-lib/src/l4d2host/process.py` -- Test: `components/l4d2-host-lib/tests/test_process.py` +- Create: `l4d2host/process.py` +- Test: `l4d2host/tests/test_process.py` - [ ] **Step 1: Write failing process tests** @@ -257,7 +257,7 @@ def test_nonzero_exit_raises(): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-host-lib/tests/test_process.py -q` +Run: `pytest l4d2host/tests/test_process.py -q` Expected: FAIL. - [ ] **Step 3: Implement process runner** @@ -304,21 +304,21 @@ def run_command(cmd, *, on_stdout=None, on_stderr=None, passthrough=False): - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-host-lib/tests/test_process.py -q` +Run: `pytest l4d2host/tests/test_process.py -q` Expected: PASS. - [ ] **Step 5: Commit process runner** ```bash -git add components/l4d2-host-lib/src/l4d2host/process.py components/l4d2-host-lib/tests/test_process.py +git add l4d2host/process.py l4d2host/tests/test_process.py git commit -m "feat(l4d2): add callback-capable streaming process runner" ``` ### Task 4: Implement install command with callback passthrough **Files:** -- Create: `components/l4d2-host-lib/src/l4d2host/steam_install.py` -- Test: `components/l4d2-host-lib/tests/test_install.py` +- Create: `l4d2host/steam_install.py` +- Test: `l4d2host/tests/test_install.py` - [ ] **Step 1: Write failing install tests** @@ -352,7 +352,7 @@ def test_fail_fast_on_first_failure(monkeypatch): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-host-lib/tests/test_install.py -q` +Run: `pytest l4d2host/tests/test_install.py -q` Expected: FAIL. - [ ] **Step 3: Implement installer** @@ -381,23 +381,23 @@ class SteamInstaller: - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-host-lib/tests/test_install.py -q` +Run: `pytest l4d2host/tests/test_install.py -q` Expected: PASS. - [ ] **Step 5: Commit install command** ```bash -git add components/l4d2-host-lib/src/l4d2host/steam_install.py components/l4d2-host-lib/tests/test_install.py +git add l4d2host/steam_install.py l4d2host/tests/test_install.py git commit -m "feat(l4d2): implement callback-aware install command" ``` ### Task 5: Implement initialize and systemd template management **Files:** -- Create: `components/l4d2-host-lib/src/l4d2host/systemd_user.py` -- Create: `components/l4d2-host-lib/src/l4d2host/instances.py` -- Create: `components/l4d2-host-lib/src/l4d2host/templates/l4d2@.service` -- Test: `components/l4d2-host-lib/tests/test_initialize.py` +- Create: `l4d2host/systemd_user.py` +- Create: `l4d2host/instances.py` +- Create: `l4d2host/templates/l4d2@.service` +- Test: `l4d2host/tests/test_initialize.py` - [ ] **Step 1: Write failing initialize tests** @@ -423,7 +423,7 @@ def test_empty_config_writes_empty_server_cfg(tmp_path: Path): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-host-lib/tests/test_initialize.py -q` +Run: `pytest l4d2host/tests/test_initialize.py -q` Expected: FAIL. - [ ] **Step 3: Implement initialize flow** @@ -458,23 +458,23 @@ def initialize_instance(name: str, spec_path: Path, *, root: Path = Path("/opt/l - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-host-lib/tests/test_initialize.py -q` +Run: `pytest l4d2host/tests/test_initialize.py -q` Expected: PASS. - [ ] **Step 5: Commit initialize logic** ```bash -git add components/l4d2-host-lib/src/l4d2host/{instances.py,systemd_user.py,templates/l4d2@.service} components/l4d2-host-lib/tests/test_initialize.py +git add l4d2host/{instances.py,systemd_user.py,templates/l4d2@.service} l4d2host/tests/test_initialize.py git commit -m "feat(l4d2): implement initialize flow and systemd user template management" ``` ### Task 6: Implement start/stop/delete lifecycle commands **Files:** -- Create: `components/l4d2-host-lib/src/l4d2host/fs/base.py` -- Create: `components/l4d2-host-lib/src/l4d2host/fs/fuse_overlayfs.py` -- Modify: `components/l4d2-host-lib/src/l4d2host/instances.py` -- Test: `components/l4d2-host-lib/tests/test_lifecycle.py` +- Create: `l4d2host/fs/base.py` +- Create: `l4d2host/fs/fuse_overlayfs.py` +- Modify: `l4d2host/instances.py` +- Test: `l4d2host/tests/test_lifecycle.py` - [ ] **Step 1: Write failing lifecycle tests** @@ -498,7 +498,7 @@ def test_delete_missing_is_noop(tmp_path: Path): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-host-lib/tests/test_lifecycle.py -q` +Run: `pytest l4d2host/tests/test_lifecycle.py -q` Expected: FAIL. - [ ] **Step 3: Implement lifecycle methods with callbacks** @@ -552,23 +552,23 @@ def delete_instance(name: str, *, root: Path = Path("/opt/l4d2"), on_stdout=None - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-host-lib/tests/test_lifecycle.py -q` +Run: `pytest l4d2host/tests/test_lifecycle.py -q` Expected: PASS. - [ ] **Step 5: Commit lifecycle implementation** ```bash -git add components/l4d2-host-lib/src/l4d2host/{fs/base.py,fs/fuse_overlayfs.py,instances.py} components/l4d2-host-lib/tests/test_lifecycle.py +git add l4d2host/{fs/base.py,fs/fuse_overlayfs.py,instances.py} l4d2host/tests/test_lifecycle.py git commit -m "feat(l4d2): implement start stop delete lifecycle with callback support" ``` ### Task 7: Add status and log read APIs **Files:** -- Create: `components/l4d2-host-lib/src/l4d2host/status.py` -- Create: `components/l4d2-host-lib/src/l4d2host/logs.py` -- Test: `components/l4d2-host-lib/tests/test_status.py` -- Test: `components/l4d2-host-lib/tests/test_logs.py` +- Create: `l4d2host/status.py` +- Create: `l4d2host/logs.py` +- Test: `l4d2host/tests/test_status.py` +- Test: `l4d2host/tests/test_logs.py` - [ ] **Step 1: Write failing read-API tests** @@ -584,7 +584,7 @@ def test_status_mapping(): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-host-lib/tests/test_status.py components/l4d2-host-lib/tests/test_logs.py -q` +Run: `pytest l4d2host/tests/test_status.py l4d2host/tests/test_logs.py -q` Expected: FAIL. - [ ] **Step 3: Implement status/log readers** @@ -660,22 +660,22 @@ def stream_instance_logs(name: str, *, lines: int = 200, follow: bool = True): - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-host-lib/tests/test_status.py components/l4d2-host-lib/tests/test_logs.py -q` +Run: `pytest l4d2host/tests/test_status.py l4d2host/tests/test_logs.py -q` Expected: PASS. - [ ] **Step 5: Commit read APIs** ```bash -git add components/l4d2-host-lib/src/l4d2host/{status.py,logs.py} components/l4d2-host-lib/tests/test_{status,logs}.py +git add l4d2host/{status.py,logs.py} l4d2host/tests/test_{status,logs}.py git commit -m "feat(l4d2): add status and journald log read APIs" ``` ### Task 8: Wire CLI to implementations and finalize docs **Files:** -- Modify: `components/l4d2-host-lib/src/l4d2host/cli.py` -- Create: `components/l4d2-host-lib/README.md` -- Modify: tests under `components/l4d2-host-lib/tests/` +- Modify: `l4d2host/cli.py` +- Create: `l4d2host/README.md` +- Modify: tests under `l4d2host/tests/` - [ ] **Step 1: Write failing CLI exit-code test** @@ -696,7 +696,7 @@ def test_cli_propagates_subprocess_return_code(monkeypatch): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-host-lib/tests/test_cli.py -q` +Run: `pytest l4d2host/tests/test_cli.py -q` Expected: FAIL. - [ ] **Step 3: Implement CLI handlers** @@ -720,13 +720,13 @@ def start(name: str) -> None: - [ ] **Step 4: Final test run** -Run: `pytest components/l4d2-host-lib/tests -q` +Run: `pytest l4d2host/tests -q` Expected: PASS. - [ ] **Step 5: Commit finalization** ```bash -git add components/l4d2-host-lib +git add l4d2host git commit -m "docs(l4d2): finalize v1 CLI contracts and web-facing read APIs" ``` diff --git a/docs/superpowers/plans/2026-04-23-l4d2-web-app-v1.md b/docs/superpowers/plans/2026-04-23-l4d2-web-app-v1.md index 26c424e..9784285 100644 --- a/docs/superpowers/plans/2026-04-23-l4d2-web-app-v1.md +++ b/docs/superpowers/plans/2026-04-23-l4d2-web-app-v1.md @@ -42,41 +42,41 @@ ## Planned File Layout -- `components/l4d2-web-app/pyproject.toml` -- `components/l4d2-web-app/src/l4d2web/app.py` -- `components/l4d2-web-app/src/l4d2web/config.py` -- `components/l4d2-web-app/src/l4d2web/db.py` -- `components/l4d2-web-app/src/l4d2web/models.py` -- `components/l4d2-web-app/src/l4d2web/auth.py` -- `components/l4d2-web-app/src/l4d2web/cli.py` -- `components/l4d2-web-app/src/l4d2web/services/l4d2_facade.py` -- `components/l4d2-web-app/src/l4d2web/services/spec_yaml.py` -- `components/l4d2-web-app/src/l4d2web/services/job_worker.py` -- `components/l4d2-web-app/src/l4d2web/services/status.py` -- `components/l4d2-web-app/src/l4d2web/services/security.py` -- `components/l4d2-web-app/src/l4d2web/routes/auth_routes.py` -- `components/l4d2-web-app/src/l4d2web/routes/overlay_routes.py` -- `components/l4d2-web-app/src/l4d2web/routes/blueprint_routes.py` -- `components/l4d2-web-app/src/l4d2web/routes/server_routes.py` -- `components/l4d2-web-app/src/l4d2web/routes/job_routes.py` -- `components/l4d2-web-app/src/l4d2web/routes/log_routes.py` -- `components/l4d2-web-app/src/l4d2web/templates/*.html` -- `components/l4d2-web-app/src/l4d2web/static/vendor/htmx.min.js` -- `components/l4d2-web-app/src/l4d2web/static/css/{tokens,layout,components,logs}.css` -- `components/l4d2-web-app/src/l4d2web/static/js/{sse,csrf}.js` -- `components/l4d2-web-app/alembic.ini` -- `components/l4d2-web-app/alembic/env.py` -- `components/l4d2-web-app/alembic/versions/0001_initial.py` -- `components/l4d2-web-app/tests/*.py` -- `components/l4d2-web-app/README.md` +- `l4d2web/pyproject.toml` +- `l4d2web/app.py` +- `l4d2web/config.py` +- `l4d2web/db.py` +- `l4d2web/models.py` +- `l4d2web/auth.py` +- `l4d2web/cli.py` +- `l4d2web/services/l4d2_facade.py` +- `l4d2web/services/spec_yaml.py` +- `l4d2web/services/job_worker.py` +- `l4d2web/services/status.py` +- `l4d2web/services/security.py` +- `l4d2web/routes/auth_routes.py` +- `l4d2web/routes/overlay_routes.py` +- `l4d2web/routes/blueprint_routes.py` +- `l4d2web/routes/server_routes.py` +- `l4d2web/routes/job_routes.py` +- `l4d2web/routes/log_routes.py` +- `l4d2web/templates/*.html` +- `l4d2web/static/vendor/htmx.min.js` +- `l4d2web/static/css/{tokens,layout,components,logs}.css` +- `l4d2web/static/js/{sse,csrf}.js` +- `l4d2web/alembic.ini` +- `l4d2web/alembic/env.py` +- `l4d2web/alembic/versions/0001_initial.py` +- `l4d2web/tests/*.py` +- `l4d2web/README.md` ### Task 1: Scaffold Flask app and base wiring **Files:** -- Create: `components/l4d2-web-app/pyproject.toml` -- Create: `components/l4d2-web-app/src/l4d2web/app.py` -- Create: `components/l4d2-web-app/src/l4d2web/config.py` -- Test: `components/l4d2-web-app/tests/test_health.py` +- Create: `l4d2web/pyproject.toml` +- Create: `l4d2web/app.py` +- Create: `l4d2web/config.py` +- Test: `l4d2web/tests/test_health.py` - [ ] **Step 1: Write failing health test** @@ -94,7 +94,7 @@ def test_health_endpoint(): - [ ] **Step 2: Run test and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_health.py -q` +Run: `pytest l4d2web/tests/test_health.py -q` Expected: FAIL. - [ ] **Step 3: Implement app factory** @@ -125,25 +125,25 @@ def create_app(test_config=None): - [ ] **Step 4: Run test and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_health.py -q` +Run: `pytest l4d2web/tests/test_health.py -q` Expected: PASS. - [ ] **Step 5: Commit scaffold** ```bash -git add components/l4d2-web-app +git add l4d2web git commit -m "feat(l4d2-web): scaffold flask app and health endpoint" ``` ### Task 2: Add database models and migration baseline **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/db.py` -- Create: `components/l4d2-web-app/src/l4d2web/models.py` -- Create: `components/l4d2-web-app/alembic.ini` -- Create: `components/l4d2-web-app/alembic/env.py` -- Create: `components/l4d2-web-app/alembic/versions/0001_initial.py` -- Test: `components/l4d2-web-app/tests/test_models.py` +- Create: `l4d2web/db.py` +- Create: `l4d2web/models.py` +- Create: `l4d2web/alembic.ini` +- Create: `l4d2web/alembic/env.py` +- Create: `l4d2web/alembic/versions/0001_initial.py` +- Test: `l4d2web/tests/test_models.py` - [ ] **Step 1: Write failing model tests** @@ -168,7 +168,7 @@ def test_create_user_and_blueprint(tmp_path, monkeypatch): - [ ] **Step 2: Run test and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_models.py -q` +Run: `pytest l4d2web/tests/test_models.py -q` Expected: FAIL. - [ ] **Step 3: Implement schema** @@ -255,24 +255,24 @@ class JobLog(Base): - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_models.py -q` +Run: `pytest l4d2web/tests/test_models.py -q` Expected: PASS. - [ ] **Step 5: Commit models and migration** ```bash -git add components/l4d2-web-app/src/l4d2web/{db.py,models.py} components/l4d2-web-app/alembic* +git add l4d2web/{db.py,models.py} l4d2web/alembic* git commit -m "feat(l4d2-web): add sqlite schema including blueprints and job logs" ``` ### Task 3: Implement auth and admin bootstrap **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/auth.py` -- Create: `components/l4d2-web-app/src/l4d2web/routes/auth_routes.py` -- Create: `components/l4d2-web-app/src/l4d2web/cli.py` -- Modify: `components/l4d2-web-app/src/l4d2web/app.py` -- Test: `components/l4d2-web-app/tests/test_auth.py` +- Create: `l4d2web/auth.py` +- Create: `l4d2web/routes/auth_routes.py` +- Create: `l4d2web/cli.py` +- Modify: `l4d2web/app.py` +- Test: `l4d2web/tests/test_auth.py` - [ ] **Step 1: Write failing auth tests** @@ -291,7 +291,7 @@ def test_login_sets_session(client, seed_user): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_auth.py -q` +Run: `pytest l4d2web/tests/test_auth.py -q` Expected: FAIL. - [ ] **Step 3: Implement auth and CLI command** @@ -322,22 +322,22 @@ def promote_admin(username: str): - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_auth.py -q` +Run: `pytest l4d2web/tests/test_auth.py -q` Expected: PASS. - [ ] **Step 5: Commit auth features** ```bash -git add components/l4d2-web-app/src/l4d2web/{auth.py,cli.py,app.py} components/l4d2-web-app/src/l4d2web/routes/auth_routes.py components/l4d2-web-app/tests/test_auth.py +git add l4d2web/{auth.py,cli.py,app.py} l4d2web/routes/auth_routes.py l4d2web/tests/test_auth.py git commit -m "feat(l4d2-web): add public auth and admin bootstrap command" ``` ### Task 4: Implement admin overlay catalog CRUD with path safety **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/routes/overlay_routes.py` -- Create: `components/l4d2-web-app/src/l4d2web/services/security.py` -- Test: `components/l4d2-web-app/tests/test_overlays.py` +- Create: `l4d2web/routes/overlay_routes.py` +- Create: `l4d2web/services/security.py` +- Test: `l4d2web/tests/test_overlays.py` - [ ] **Step 1: Write failing overlay tests** @@ -354,7 +354,7 @@ def test_overlay_path_must_be_under_root(admin_client): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_overlays.py -q` +Run: `pytest l4d2web/tests/test_overlays.py -q` Expected: FAIL. - [ ] **Step 3: Implement route and validator** @@ -372,21 +372,21 @@ def validate_overlay_path(raw: str) -> Path: - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_overlays.py -q` +Run: `pytest l4d2web/tests/test_overlays.py -q` Expected: PASS. - [ ] **Step 5: Commit overlay CRUD** ```bash -git add components/l4d2-web-app/src/l4d2web/{routes/overlay_routes.py,services/security.py} components/l4d2-web-app/tests/test_overlays.py +git add l4d2web/{routes/overlay_routes.py,services/security.py} l4d2web/tests/test_overlays.py git commit -m "feat(l4d2-web): add admin overlay catalog CRUD with path validation" ``` ### Task 5: Implement blueprint CRUD and linkage rules **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/routes/blueprint_routes.py` -- Test: `components/l4d2-web-app/tests/test_blueprints.py` +- Create: `l4d2web/routes/blueprint_routes.py` +- Test: `l4d2web/tests/test_blueprints.py` - [ ] **Step 1: Write failing blueprint tests** @@ -409,7 +409,7 @@ def test_delete_blueprint_blocked_when_in_use(user_client, linked_blueprint): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_blueprints.py -q` +Run: `pytest l4d2web/tests/test_blueprints.py -q` Expected: FAIL. - [ ] **Step 3: Implement blueprint routes** @@ -436,21 +436,21 @@ def create_blueprint(): - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_blueprints.py -q` +Run: `pytest l4d2web/tests/test_blueprints.py -q` Expected: PASS. - [ ] **Step 5: Commit blueprint feature** ```bash -git add components/l4d2-web-app/src/l4d2web/routes/blueprint_routes.py components/l4d2-web-app/tests/test_blueprints.py +git add l4d2web/routes/blueprint_routes.py l4d2web/tests/test_blueprints.py git commit -m "feat(l4d2-web): add private blueprint CRUD with in-use deletion guard" ``` ### Task 6: Implement server CRUD from blueprints (no overrides) **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/routes/server_routes.py` -- Test: `components/l4d2-web-app/tests/test_servers.py` +- Create: `l4d2web/routes/server_routes.py` +- Test: `l4d2web/tests/test_servers.py` - [ ] **Step 1: Write failing server tests** @@ -468,7 +468,7 @@ def test_reassign_blueprint_anytime(user_client, server_and_two_blueprints): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_servers.py -q` +Run: `pytest l4d2web/tests/test_servers.py -q` Expected: FAIL. - [ ] **Step 3: Implement server routes** @@ -495,22 +495,22 @@ def create_server(): - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_servers.py -q` +Run: `pytest l4d2web/tests/test_servers.py -q` Expected: PASS. - [ ] **Step 5: Commit server routes** ```bash -git add components/l4d2-web-app/src/l4d2web/routes/server_routes.py components/l4d2-web-app/tests/test_servers.py +git add l4d2web/routes/server_routes.py l4d2web/tests/test_servers.py git commit -m "feat(l4d2-web): add server creation and blueprint reassignment routes" ``` ### Task 7: Add direct `l4d2host` facade and blueprint-to-spec generation **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/services/spec_yaml.py` -- Create: `components/l4d2-web-app/src/l4d2web/services/l4d2_facade.py` -- Test: `components/l4d2-web-app/tests/test_l4d2_facade.py` +- Create: `l4d2web/services/spec_yaml.py` +- Create: `l4d2web/services/l4d2_facade.py` +- Test: `l4d2web/tests/test_l4d2_facade.py` - [ ] **Step 1: Write failing facade tests** @@ -531,7 +531,7 @@ def test_initialize_uses_latest_blueprint_data(monkeypatch, server_with_blueprin - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_l4d2_facade.py -q` +Run: `pytest l4d2web/tests/test_l4d2_facade.py -q` Expected: FAIL. - [ ] **Step 3: Implement facade/spec generation** @@ -555,22 +555,22 @@ def initialize_server(server_id: int, on_stdout=None, on_stderr=None): - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_l4d2_facade.py -q` +Run: `pytest l4d2web/tests/test_l4d2_facade.py -q` Expected: PASS. - [ ] **Step 5: Commit facade** ```bash -git add components/l4d2-web-app/src/l4d2web/services/{spec_yaml.py,l4d2_facade.py} components/l4d2-web-app/tests/test_l4d2_facade.py +git add l4d2web/services/{spec_yaml.py,l4d2_facade.py} l4d2web/tests/test_l4d2_facade.py git commit -m "feat(l4d2-web): resolve live-linked blueprints to runtime specs via l4d2host" ``` ### Task 8: Implement scheduler, lock rules, and startup recovery **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/services/job_worker.py` -- Modify: `components/l4d2-web-app/src/l4d2web/app.py` -- Test: `components/l4d2-web-app/tests/test_job_worker.py` +- Create: `l4d2web/services/job_worker.py` +- Modify: `l4d2web/app.py` +- Test: `l4d2web/tests/test_job_worker.py` - [ ] **Step 1: Write failing worker tests** @@ -597,7 +597,7 @@ def test_recover_stale_running_jobs(worker_fixture): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_job_worker.py -q` +Run: `pytest l4d2web/tests/test_job_worker.py -q` Expected: FAIL. - [ ] **Step 3: Implement scheduler with in-memory guards** @@ -619,22 +619,22 @@ def can_start(job, state: SchedulerState) -> bool: - [ ] **Step 4: Implement single-process guard + stale recovery** -Run: `pytest components/l4d2-web-app/tests/test_job_worker.py -q` +Run: `pytest l4d2web/tests/test_job_worker.py -q` Expected: PASS. - [ ] **Step 5: Commit scheduler** ```bash -git add components/l4d2-web-app/src/l4d2web/{services/job_worker.py,app.py} components/l4d2-web-app/tests/test_job_worker.py +git add l4d2web/{services/job_worker.py,app.py} l4d2web/tests/test_job_worker.py git commit -m "feat(l4d2-web): add async scheduler with lock rules and crash recovery" ``` ### Task 9: Persist command logs and add SSE job stream **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/routes/job_routes.py` -- Modify: `components/l4d2-web-app/src/l4d2web/services/job_worker.py` -- Test: `components/l4d2-web-app/tests/test_job_logs.py` +- Create: `l4d2web/routes/job_routes.py` +- Modify: `l4d2web/services/job_worker.py` +- Test: `l4d2web/tests/test_job_logs.py` - [ ] **Step 1: Write failing job-log tests** @@ -652,7 +652,7 @@ def test_sse_resume_from_last_seq(client, seeded_job_logs): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_job_logs.py -q` +Run: `pytest l4d2web/tests/test_job_logs.py -q` Expected: FAIL. - [ ] **Step 3: Implement persistence and SSE route** @@ -689,23 +689,23 @@ def stream_job(job_id: int): - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_job_logs.py -q` +Run: `pytest l4d2web/tests/test_job_logs.py -q` Expected: PASS. - [ ] **Step 5: Commit job log streaming** ```bash -git add components/l4d2-web-app/src/l4d2web/{routes/job_routes.py,services/job_worker.py} components/l4d2-web-app/tests/test_job_logs.py +git add l4d2web/{routes/job_routes.py,services/job_worker.py} l4d2web/tests/test_job_logs.py git commit -m "feat(l4d2-web): persist command logs and stream them with sse" ``` ### Task 10: Add runtime server log stream and status model **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/routes/log_routes.py` -- Create: `components/l4d2-web-app/src/l4d2web/services/status.py` -- Modify: `components/l4d2-web-app/src/l4d2web/services/job_worker.py` -- Test: `components/l4d2-web-app/tests/test_status_and_server_logs.py` +- Create: `l4d2web/routes/log_routes.py` +- Create: `l4d2web/services/status.py` +- Modify: `l4d2web/services/job_worker.py` +- Test: `l4d2web/tests/test_status_and_server_logs.py` - [ ] **Step 1: Write failing tests** @@ -722,7 +722,7 @@ def test_status_precedence(): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_status_and_server_logs.py -q` +Run: `pytest l4d2web/tests/test_status_and_server_logs.py -q` Expected: FAIL. - [ ] **Step 3: Implement log stream and status computation** @@ -759,23 +759,23 @@ def stream_server_logs(server_id: int): - [ ] **Step 4: Add actual-state refresh updates** -Run: `pytest components/l4d2-web-app/tests/test_status_and_server_logs.py -q` +Run: `pytest l4d2web/tests/test_status_and_server_logs.py -q` Expected: PASS. - [ ] **Step 5: Commit status/log features** ```bash -git add components/l4d2-web-app/src/l4d2web/{routes/log_routes.py,services/status.py,services/job_worker.py} components/l4d2-web-app/tests/test_status_and_server_logs.py +git add l4d2web/{routes/log_routes.py,services/status.py,services/job_worker.py} l4d2web/tests/test_status_and_server_logs.py git commit -m "feat(l4d2-web): add live server logs and desired-vs-actual status model" ``` ### Task 11: Apply security and reliability hardening **Files:** -- Modify: `components/l4d2-web-app/src/l4d2web/app.py` -- Modify: `components/l4d2-web-app/src/l4d2web/db.py` -- Modify: `components/l4d2-web-app/src/l4d2web/routes/auth_routes.py` -- Create: `components/l4d2-web-app/tests/test_security.py` +- Modify: `l4d2web/app.py` +- Modify: `l4d2web/db.py` +- Modify: `l4d2web/routes/auth_routes.py` +- Create: `l4d2web/tests/test_security.py` - [ ] **Step 1: Write failing hardening tests** @@ -794,7 +794,7 @@ def test_login_rate_limit(client): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_security.py -q` +Run: `pytest l4d2web/tests/test_security.py -q` Expected: FAIL. - [ ] **Step 3: Implement hardening measures** @@ -815,33 +815,33 @@ with engine.connect() as conn: - [ ] **Step 4: Run tests and verify pass** -Run: `pytest components/l4d2-web-app/tests/test_security.py -q` +Run: `pytest l4d2web/tests/test_security.py -q` Expected: PASS. - [ ] **Step 5: Commit hardening** ```bash -git add components/l4d2-web-app/src/l4d2web/{app.py,db.py} components/l4d2-web-app/src/l4d2web/routes/auth_routes.py components/l4d2-web-app/tests/test_security.py +git add l4d2web/{app.py,db.py} l4d2web/routes/auth_routes.py l4d2web/tests/test_security.py git commit -m "feat(l4d2-web): add csrf, rate limiting, and sqlite reliability settings" ``` ### Task 12: Build UI and finalize docs **Files:** -- Create: `components/l4d2-web-app/src/l4d2web/templates/base.html` -- Create: `components/l4d2-web-app/src/l4d2web/templates/dashboard.html` -- Create: `components/l4d2-web-app/src/l4d2web/templates/blueprints.html` -- Create: `components/l4d2-web-app/src/l4d2web/templates/server_detail.html` -- Create: `components/l4d2-web-app/src/l4d2web/templates/admin_overlays.html` -- Create: `components/l4d2-web-app/src/l4d2web/static/vendor/htmx.min.js` -- Create: `components/l4d2-web-app/src/l4d2web/static/css/tokens.css` -- Create: `components/l4d2-web-app/src/l4d2web/static/css/layout.css` -- Create: `components/l4d2-web-app/src/l4d2web/static/css/components.css` -- Create: `components/l4d2-web-app/src/l4d2web/static/css/logs.css` -- Create: `components/l4d2-web-app/src/l4d2web/static/js/sse.js` -- Create: `components/l4d2-web-app/src/l4d2web/static/js/csrf.js` -- Create: `components/l4d2-web-app/README.md` -- Test: `components/l4d2-web-app/tests/test_pages.py` +- Create: `l4d2web/templates/base.html` +- Create: `l4d2web/templates/dashboard.html` +- Create: `l4d2web/templates/blueprints.html` +- Create: `l4d2web/templates/server_detail.html` +- Create: `l4d2web/templates/admin_overlays.html` +- Create: `l4d2web/static/vendor/htmx.min.js` +- Create: `l4d2web/static/css/tokens.css` +- Create: `l4d2web/static/css/layout.css` +- Create: `l4d2web/static/css/components.css` +- Create: `l4d2web/static/css/logs.css` +- Create: `l4d2web/static/js/sse.js` +- Create: `l4d2web/static/js/csrf.js` +- Create: `l4d2web/README.md` +- Test: `l4d2web/tests/test_pages.py` - [ ] **Step 1: Write failing page tests** @@ -860,7 +860,7 @@ def test_blueprint_page_private_visibility(user_client, other_users_blueprint): - [ ] **Step 2: Run tests and verify failure** -Run: `pytest components/l4d2-web-app/tests/test_pages.py -q` +Run: `pytest l4d2web/tests/test_pages.py -q` Expected: FAIL. - [ ] **Step 3: Implement templates and style tokens** @@ -883,13 +883,13 @@ a { - [ ] **Step 4: Run tests and full suite** -Run: `pytest components/l4d2-web-app/tests -q` +Run: `pytest l4d2web/tests -q` Expected: PASS. - [ ] **Step 5: Commit UI and docs** ```bash -git add components/l4d2-web-app +git add l4d2web git commit -m "docs(l4d2-web): finalize blueprint-driven ui and deployment contracts" ``` diff --git a/docs/superpowers/plans/2026-05-05-l4d2-host-smoke-test.md b/docs/superpowers/plans/2026-05-05-l4d2-host-smoke-test.md index 38f9b27..b90e4ff 100644 --- a/docs/superpowers/plans/2026-05-05-l4d2-host-smoke-test.md +++ b/docs/superpowers/plans/2026-05-05-l4d2-host-smoke-test.md @@ -39,8 +39,8 @@ Do not perform cleanup after a failure unless the user approves cleanup. ## Files And Runtime Locations - Read: `docs/superpowers/specs/2026-05-05-l4d2-host-smoke-test-design.md` -- Read: `components/l4d2-host-lib/pyproject.toml` -- Read: `components/l4d2-host-lib/src/l4d2host/**` +- Read: `l4d2host/pyproject.toml` +- Read: `l4d2host/**` - Remote create: `~/l4d2host-smoke/` - Remote create: `~/l4d2host-smoke/.venv/` - Remote create: `~/l4d2host-smoke/specs/smoke.yaml` @@ -238,7 +238,7 @@ Expected: user explicitly approves before commands are run. Run from repository root: ```bash -tar --exclude='*.pyc' --exclude='__pycache__' --exclude='.pytest_cache' --exclude='*.egg-info' -C components/l4d2-host-lib -czf /var/folders/h4/nnvk2kxs2sv7nr32kmb_4dm40000gn/T/opencode/l4d2-host-lib-smoke.tar.gz . +tar --exclude='*.pyc' --exclude='__pycache__' --exclude='.pytest_cache' --exclude='*.egg-info' -C l4d2host -czf /var/folders/h4/nnvk2kxs2sv7nr32kmb_4dm40000gn/T/opencode/l4d2-host-lib-smoke.tar.gz . ``` Expected: command exits 0 and archive exists at `/var/folders/h4/nnvk2kxs2sv7nr32kmb_4dm40000gn/T/opencode/l4d2-host-lib-smoke.tar.gz`. @@ -264,7 +264,7 @@ Expected: archive copies successfully. Run: ```bash -ssh ckn@10.0.4.128 'set -eu; tar -xzf "$HOME/l4d2host-smoke/l4d2-host-lib-smoke.tar.gz" -C "$HOME/l4d2host-smoke/src"; test -f "$HOME/l4d2host-smoke/src/pyproject.toml"; test -f "$HOME/l4d2host-smoke/src/src/l4d2host/cli.py"' +ssh ckn@10.0.4.128 'set -eu; tar -xzf "$HOME/l4d2host-smoke/l4d2-host-lib-smoke.tar.gz" -C "$HOME/l4d2host-smoke/src"; test -f "$HOME/l4d2host-smoke/src/pyproject.toml"; test -f "$HOME/l4d2host-smoke/src/cli.py"' ``` Expected: source tree unpacks and expected files exist. diff --git a/docs/superpowers/specs/2026-05-05-l4d2-host-smoke-test-design.md b/docs/superpowers/specs/2026-05-05-l4d2-host-smoke-test-design.md index 6d45f5b..0a87d8f 100644 --- a/docs/superpowers/specs/2026-05-05-l4d2-host-smoke-test-design.md +++ b/docs/superpowers/specs/2026-05-05-l4d2-host-smoke-test-design.md @@ -12,8 +12,8 @@ The repository now contains both planned components: -- `components/l4d2-host-lib`: Python host library and `l4d2ctl` CLI. -- `components/l4d2-web-app`: Flask app for users, blueprints, servers, jobs, and logs. +- `l4d2host`: Python host library and `l4d2ctl` CLI. +- `l4d2web`: Flask app for users, blueprints, servers, jobs, and logs. The web app depends on the host library for real lifecycle behavior. Before wiring web lifecycle jobs end-to-end, the host contract should be proven on an actual Linux machine with `steamcmd`, `fuse-overlayfs`, systemd user services, and journald available. @@ -81,7 +81,7 @@ Purpose: install the current repository implementation on the target host withou Allowed actions after approval: -- Copy or archive the current `components/l4d2-host-lib` source to the server. +- Copy or archive the current `l4d2host` source to the server. - Install it using its existing `pyproject.toml`, preferably into an isolated virtual environment. - Verify that `l4d2ctl --help` exposes the fixed v1 command surface. diff --git a/components/l4d2-host-lib/README.md b/l4d2host/README.md similarity index 100% rename from components/l4d2-host-lib/README.md rename to l4d2host/README.md diff --git a/components/l4d2-host-lib/src/l4d2host/__init__.py b/l4d2host/__init__.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/__init__.py rename to l4d2host/__init__.py diff --git a/components/l4d2-host-lib/src/l4d2host/cli.py b/l4d2host/cli.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/cli.py rename to l4d2host/cli.py diff --git a/components/l4d2-host-lib/src/l4d2host/fs/__init__.py b/l4d2host/fs/__init__.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/fs/__init__.py rename to l4d2host/fs/__init__.py diff --git a/components/l4d2-host-lib/src/l4d2host/fs/base.py b/l4d2host/fs/base.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/fs/base.py rename to l4d2host/fs/base.py diff --git a/components/l4d2-host-lib/src/l4d2host/fs/fuse_overlayfs.py b/l4d2host/fs/fuse_overlayfs.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/fs/fuse_overlayfs.py rename to l4d2host/fs/fuse_overlayfs.py diff --git a/components/l4d2-host-lib/src/l4d2host/instances.py b/l4d2host/instances.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/instances.py rename to l4d2host/instances.py diff --git a/components/l4d2-host-lib/src/l4d2host/logs.py b/l4d2host/logs.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/logs.py rename to l4d2host/logs.py diff --git a/components/l4d2-host-lib/src/l4d2host/process.py b/l4d2host/process.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/process.py rename to l4d2host/process.py diff --git a/components/l4d2-host-lib/pyproject.toml b/l4d2host/pyproject.toml similarity index 66% rename from components/l4d2-host-lib/pyproject.toml rename to l4d2host/pyproject.toml index d0ecc61..c38d1c1 100644 --- a/components/l4d2-host-lib/pyproject.toml +++ b/l4d2host/pyproject.toml @@ -17,7 +17,10 @@ dependencies = [ l4d2ctl = "l4d2host.cli:app" [tool.setuptools] -package-dir = {"" = "src"} +packages = ["l4d2host", "l4d2host.fs", "l4d2host.templates"] -[tool.setuptools.packages.find] -where = ["src"] +[tool.setuptools.package-dir] +l4d2host = "." + +[tool.setuptools.package-data] +"l4d2host.templates" = ["*.service"] diff --git a/components/l4d2-host-lib/src/l4d2host/spec.py b/l4d2host/spec.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/spec.py rename to l4d2host/spec.py diff --git a/components/l4d2-host-lib/src/l4d2host/status.py b/l4d2host/status.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/status.py rename to l4d2host/status.py diff --git a/components/l4d2-host-lib/src/l4d2host/steam_install.py b/l4d2host/steam_install.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/steam_install.py rename to l4d2host/steam_install.py diff --git a/components/l4d2-host-lib/src/l4d2host/systemd_user.py b/l4d2host/systemd_user.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/systemd_user.py rename to l4d2host/systemd_user.py diff --git a/components/l4d2-host-lib/src/l4d2host/templates/__init__.py b/l4d2host/templates/__init__.py similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/templates/__init__.py rename to l4d2host/templates/__init__.py diff --git a/components/l4d2-host-lib/src/l4d2host/templates/l4d2@.service b/l4d2host/templates/l4d2@.service similarity index 100% rename from components/l4d2-host-lib/src/l4d2host/templates/l4d2@.service rename to l4d2host/templates/l4d2@.service diff --git a/components/l4d2-host-lib/tests/test_cli.py b/l4d2host/tests/test_cli.py similarity index 100% rename from components/l4d2-host-lib/tests/test_cli.py rename to l4d2host/tests/test_cli.py diff --git a/components/l4d2-host-lib/tests/test_initialize.py b/l4d2host/tests/test_initialize.py similarity index 100% rename from components/l4d2-host-lib/tests/test_initialize.py rename to l4d2host/tests/test_initialize.py diff --git a/components/l4d2-host-lib/tests/test_install.py b/l4d2host/tests/test_install.py similarity index 100% rename from components/l4d2-host-lib/tests/test_install.py rename to l4d2host/tests/test_install.py diff --git a/components/l4d2-host-lib/tests/test_lifecycle.py b/l4d2host/tests/test_lifecycle.py similarity index 100% rename from components/l4d2-host-lib/tests/test_lifecycle.py rename to l4d2host/tests/test_lifecycle.py diff --git a/components/l4d2-host-lib/tests/test_logs.py b/l4d2host/tests/test_logs.py similarity index 100% rename from components/l4d2-host-lib/tests/test_logs.py rename to l4d2host/tests/test_logs.py diff --git a/components/l4d2-host-lib/tests/test_process.py b/l4d2host/tests/test_process.py similarity index 100% rename from components/l4d2-host-lib/tests/test_process.py rename to l4d2host/tests/test_process.py diff --git a/components/l4d2-host-lib/tests/test_spec.py b/l4d2host/tests/test_spec.py similarity index 100% rename from components/l4d2-host-lib/tests/test_spec.py rename to l4d2host/tests/test_spec.py diff --git a/components/l4d2-host-lib/tests/test_status.py b/l4d2host/tests/test_status.py similarity index 100% rename from components/l4d2-host-lib/tests/test_status.py rename to l4d2host/tests/test_status.py diff --git a/components/l4d2-web-app/README.md b/l4d2web/README.md similarity index 100% rename from components/l4d2-web-app/README.md rename to l4d2web/README.md diff --git a/components/l4d2-web-app/src/l4d2web/__init__.py b/l4d2web/__init__.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/__init__.py rename to l4d2web/__init__.py diff --git a/components/l4d2-web-app/alembic.ini b/l4d2web/alembic.ini similarity index 100% rename from components/l4d2-web-app/alembic.ini rename to l4d2web/alembic.ini diff --git a/components/l4d2-web-app/alembic/env.py b/l4d2web/alembic/env.py similarity index 100% rename from components/l4d2-web-app/alembic/env.py rename to l4d2web/alembic/env.py diff --git a/components/l4d2-web-app/alembic/versions/0001_initial.py b/l4d2web/alembic/versions/0001_initial.py similarity index 100% rename from components/l4d2-web-app/alembic/versions/0001_initial.py rename to l4d2web/alembic/versions/0001_initial.py diff --git a/components/l4d2-web-app/src/l4d2web/app.py b/l4d2web/app.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/app.py rename to l4d2web/app.py diff --git a/components/l4d2-web-app/src/l4d2web/auth.py b/l4d2web/auth.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/auth.py rename to l4d2web/auth.py diff --git a/components/l4d2-web-app/src/l4d2web/cli.py b/l4d2web/cli.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/cli.py rename to l4d2web/cli.py diff --git a/components/l4d2-web-app/src/l4d2web/config.py b/l4d2web/config.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/config.py rename to l4d2web/config.py diff --git a/components/l4d2-web-app/src/l4d2web/db.py b/l4d2web/db.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/db.py rename to l4d2web/db.py diff --git a/components/l4d2-web-app/src/l4d2web/models.py b/l4d2web/models.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/models.py rename to l4d2web/models.py diff --git a/components/l4d2-web-app/pyproject.toml b/l4d2web/pyproject.toml similarity index 57% rename from components/l4d2-web-app/pyproject.toml rename to l4d2web/pyproject.toml index a5100a4..df68325 100644 --- a/components/l4d2-web-app/pyproject.toml +++ b/l4d2web/pyproject.toml @@ -16,7 +16,15 @@ dependencies = [ ] [tool.setuptools] -package-dir = {"" = "src"} +packages = ["l4d2web", "l4d2web.routes", "l4d2web.services"] -[tool.setuptools.packages.find] -where = ["src"] +[tool.setuptools.package-dir] +l4d2web = "." + +[tool.setuptools.package-data] +l4d2web = [ + "templates/*.html", + "static/css/*.css", + "static/js/*.js", + "static/vendor/*.js", +] diff --git a/components/l4d2-web-app/src/l4d2web/routes/__init__.py b/l4d2web/routes/__init__.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/routes/__init__.py rename to l4d2web/routes/__init__.py diff --git a/components/l4d2-web-app/src/l4d2web/routes/auth_routes.py b/l4d2web/routes/auth_routes.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/routes/auth_routes.py rename to l4d2web/routes/auth_routes.py diff --git a/components/l4d2-web-app/src/l4d2web/routes/blueprint_routes.py b/l4d2web/routes/blueprint_routes.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/routes/blueprint_routes.py rename to l4d2web/routes/blueprint_routes.py diff --git a/components/l4d2-web-app/src/l4d2web/routes/job_routes.py b/l4d2web/routes/job_routes.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/routes/job_routes.py rename to l4d2web/routes/job_routes.py diff --git a/components/l4d2-web-app/src/l4d2web/routes/log_routes.py b/l4d2web/routes/log_routes.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/routes/log_routes.py rename to l4d2web/routes/log_routes.py diff --git a/components/l4d2-web-app/src/l4d2web/routes/overlay_routes.py b/l4d2web/routes/overlay_routes.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/routes/overlay_routes.py rename to l4d2web/routes/overlay_routes.py diff --git a/components/l4d2-web-app/src/l4d2web/routes/page_routes.py b/l4d2web/routes/page_routes.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/routes/page_routes.py rename to l4d2web/routes/page_routes.py diff --git a/components/l4d2-web-app/src/l4d2web/routes/server_routes.py b/l4d2web/routes/server_routes.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/routes/server_routes.py rename to l4d2web/routes/server_routes.py diff --git a/components/l4d2-web-app/src/l4d2web/services/__init__.py b/l4d2web/services/__init__.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/services/__init__.py rename to l4d2web/services/__init__.py diff --git a/components/l4d2-web-app/src/l4d2web/services/job_worker.py b/l4d2web/services/job_worker.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/services/job_worker.py rename to l4d2web/services/job_worker.py diff --git a/components/l4d2-web-app/src/l4d2web/services/l4d2_facade.py b/l4d2web/services/l4d2_facade.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/services/l4d2_facade.py rename to l4d2web/services/l4d2_facade.py diff --git a/components/l4d2-web-app/src/l4d2web/services/security.py b/l4d2web/services/security.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/services/security.py rename to l4d2web/services/security.py diff --git a/components/l4d2-web-app/src/l4d2web/services/spec_yaml.py b/l4d2web/services/spec_yaml.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/services/spec_yaml.py rename to l4d2web/services/spec_yaml.py diff --git a/components/l4d2-web-app/src/l4d2web/services/status.py b/l4d2web/services/status.py similarity index 100% rename from components/l4d2-web-app/src/l4d2web/services/status.py rename to l4d2web/services/status.py diff --git a/components/l4d2-web-app/src/l4d2web/static/css/components.css b/l4d2web/static/css/components.css similarity index 100% rename from components/l4d2-web-app/src/l4d2web/static/css/components.css rename to l4d2web/static/css/components.css diff --git a/components/l4d2-web-app/src/l4d2web/static/css/layout.css b/l4d2web/static/css/layout.css similarity index 100% rename from components/l4d2-web-app/src/l4d2web/static/css/layout.css rename to l4d2web/static/css/layout.css diff --git a/components/l4d2-web-app/src/l4d2web/static/css/logs.css b/l4d2web/static/css/logs.css similarity index 100% rename from components/l4d2-web-app/src/l4d2web/static/css/logs.css rename to l4d2web/static/css/logs.css diff --git a/components/l4d2-web-app/src/l4d2web/static/css/tokens.css b/l4d2web/static/css/tokens.css similarity index 100% rename from components/l4d2-web-app/src/l4d2web/static/css/tokens.css rename to l4d2web/static/css/tokens.css diff --git a/components/l4d2-web-app/src/l4d2web/static/js/csrf.js b/l4d2web/static/js/csrf.js similarity index 100% rename from components/l4d2-web-app/src/l4d2web/static/js/csrf.js rename to l4d2web/static/js/csrf.js diff --git a/components/l4d2-web-app/src/l4d2web/static/js/sse.js b/l4d2web/static/js/sse.js similarity index 100% rename from components/l4d2-web-app/src/l4d2web/static/js/sse.js rename to l4d2web/static/js/sse.js diff --git a/components/l4d2-web-app/src/l4d2web/static/vendor/htmx.min.js b/l4d2web/static/vendor/htmx.min.js similarity index 100% rename from components/l4d2-web-app/src/l4d2web/static/vendor/htmx.min.js rename to l4d2web/static/vendor/htmx.min.js diff --git a/components/l4d2-web-app/src/l4d2web/templates/admin_overlays.html b/l4d2web/templates/admin_overlays.html similarity index 100% rename from components/l4d2-web-app/src/l4d2web/templates/admin_overlays.html rename to l4d2web/templates/admin_overlays.html diff --git a/components/l4d2-web-app/src/l4d2web/templates/base.html b/l4d2web/templates/base.html similarity index 100% rename from components/l4d2-web-app/src/l4d2web/templates/base.html rename to l4d2web/templates/base.html diff --git a/components/l4d2-web-app/src/l4d2web/templates/blueprints.html b/l4d2web/templates/blueprints.html similarity index 100% rename from components/l4d2-web-app/src/l4d2web/templates/blueprints.html rename to l4d2web/templates/blueprints.html diff --git a/components/l4d2-web-app/src/l4d2web/templates/dashboard.html b/l4d2web/templates/dashboard.html similarity index 100% rename from components/l4d2-web-app/src/l4d2web/templates/dashboard.html rename to l4d2web/templates/dashboard.html diff --git a/components/l4d2-web-app/src/l4d2web/templates/server_detail.html b/l4d2web/templates/server_detail.html similarity index 100% rename from components/l4d2-web-app/src/l4d2web/templates/server_detail.html rename to l4d2web/templates/server_detail.html diff --git a/components/l4d2-web-app/tests/test_auth.py b/l4d2web/tests/test_auth.py similarity index 100% rename from components/l4d2-web-app/tests/test_auth.py rename to l4d2web/tests/test_auth.py diff --git a/components/l4d2-web-app/tests/test_blueprints.py b/l4d2web/tests/test_blueprints.py similarity index 100% rename from components/l4d2-web-app/tests/test_blueprints.py rename to l4d2web/tests/test_blueprints.py diff --git a/components/l4d2-web-app/tests/test_health.py b/l4d2web/tests/test_health.py similarity index 100% rename from components/l4d2-web-app/tests/test_health.py rename to l4d2web/tests/test_health.py diff --git a/components/l4d2-web-app/tests/test_job_logs.py b/l4d2web/tests/test_job_logs.py similarity index 100% rename from components/l4d2-web-app/tests/test_job_logs.py rename to l4d2web/tests/test_job_logs.py diff --git a/components/l4d2-web-app/tests/test_job_worker.py b/l4d2web/tests/test_job_worker.py similarity index 100% rename from components/l4d2-web-app/tests/test_job_worker.py rename to l4d2web/tests/test_job_worker.py diff --git a/components/l4d2-web-app/tests/test_l4d2_facade.py b/l4d2web/tests/test_l4d2_facade.py similarity index 100% rename from components/l4d2-web-app/tests/test_l4d2_facade.py rename to l4d2web/tests/test_l4d2_facade.py diff --git a/components/l4d2-web-app/tests/test_models.py b/l4d2web/tests/test_models.py similarity index 100% rename from components/l4d2-web-app/tests/test_models.py rename to l4d2web/tests/test_models.py diff --git a/components/l4d2-web-app/tests/test_overlays.py b/l4d2web/tests/test_overlays.py similarity index 100% rename from components/l4d2-web-app/tests/test_overlays.py rename to l4d2web/tests/test_overlays.py diff --git a/components/l4d2-web-app/tests/test_pages.py b/l4d2web/tests/test_pages.py similarity index 100% rename from components/l4d2-web-app/tests/test_pages.py rename to l4d2web/tests/test_pages.py diff --git a/components/l4d2-web-app/tests/test_security.py b/l4d2web/tests/test_security.py similarity index 100% rename from components/l4d2-web-app/tests/test_security.py rename to l4d2web/tests/test_security.py diff --git a/components/l4d2-web-app/tests/test_servers.py b/l4d2web/tests/test_servers.py similarity index 100% rename from components/l4d2-web-app/tests/test_servers.py rename to l4d2web/tests/test_servers.py diff --git a/components/l4d2-web-app/tests/test_status_and_server_logs.py b/l4d2web/tests/test_status_and_server_logs.py similarity index 100% rename from components/l4d2-web-app/tests/test_status_and_server_logs.py rename to l4d2web/tests/test_status_and_server_logs.py