# Agent workflow rules Companion to `copilot-instructions.md` (which owns naming/style/architecture). These cut wasted turns. All are lessons from real sessions. ## Environment - know these, do not rediscover - Shell is **PowerShell**: use `;` not `&&`, `2>$null` not `2>/dev/null`. - API container `shopdb-flask-wilm-api-1` (Flask, port `5001`). - DB container `shopdb-flask-wilm-db-1` (MySQL 8, root pw `root_password_123`). - SPA is **baked into the image** (not bind-mounted). Plugin code is **bind-mounted read-only**. - `docs/` is stripped to a public wiki mirror - keep working docs out of it. - `requirements.txt` is hash-checked - never hand-edit. ## Verify, don't react - The terminal "Command failed" banner fires on any non-zero exit or stderr output and is **usually a false negative** (`curl --fail` on a 400, pytest/npm/git writing to stderr). Judge success from real output. - Read container health via `docker inspect --format '{{.State.Health.Status}}'` plus a probe **without** `--fail` - never eyeball the exit code. ## Container runs the image, not your edits Before running anything in the container that must reflect a change: 1. Frontend: `npm run build`, then `docker cp frontend/dist/. shopdb-flask-wilm-api-1:/app/frontend/dist`. 2. Backend/scripts/templates: `docker cp` the files (or rebuild) first. If a result contradicts a change you just made, suspect stale container code before re-deriving the fix. ## Testing - Confirm the working directory once, up front. - Run the smallest test that proves the point; find the minimal polluter for isolation bugs. Run the full suite only at the end. - Clear stale local state before plugin/migration tests: `if (Test-Path instance/plugins.json) { Remove-Item instance/plugins.json -Force }`. - Naming-gate hits inside local venvs (`.ci-venv/`, `env/`) are not repo code - do not "fix" vendored libraries. - Final gates, exactly as CI: `python -m pytest tests/ -q`; `cd frontend; npx vitest run; npm run build; cd ..`; `bash scripts/check-naming-and-style.sh`. ## Migrations and imports - Every migration: idempotent guard + real downgrade; must run clean on strict MySQL 8. If you rename indexes in `upgrade()`, update `downgrade()` to match. - Avoid duplicate indexes: `index=True` plus an explicit `db.Index` on the same column creates two. - Importers/seeders match by natural key and must be idempotent - prove it by running twice (expect `updated`, not `created`). Watch all-or-nothing writes. ## Don't offer rollbacks you don't need State current state plainly; surface a rollback path only for a real regression. ## Wilmington (wilm) local-run known failures - do not re-investigate Running the full `python -m pytest tests/ -q` on a Windows dev box (not CI) produces a stable set of environmental failures that are NOT code regressions. Treat these as expected locally; judge your change against CI/Linux instead: - `test_core/test_security_headers.py::test_a_served_upload_is_sandboxed` - `PermissionError [WinError 32]` (served upload file still locked by another process). Windows file-lock timing, not a headers bug. - `test_plugin_contract.py::test_import_scan_covers_symlinked_plugins` - `OSError [WinError 1314]` (symlink privilege not held). Needs admin/Developer Mode to create symlinks; irrelevant to plugin logic. - `test_plugin_importguard.py::test_verified_source_signed_ok_tampered_raises` - CRLF vs LF (`b'# plugin\r\n' == b'# plugin\n'`). Git autocrlf checkout, not a signing bug. - `test_docs_publishable.py` and `test_docs_versions.py` - the local `docs/` tree is not the stripped public wiki mirror, so doc-count / PROJECT-MAP assertions fail. Expected off-CI. - `test_core/test_public_read_surface.py::test_every_get_route_is_gated_or_named_public` - pre-existing ungated-GET inventory drift; only act on it if YOUR change added a new GET route. If a run shows ONLY these, the suite is green for our purposes. Anything outside this list is real - investigate it.