forked from retoor/devplacepy
feat: add --changed fast mode to maintenance agents restricting scope to git-modified files under devplacepy/ and tests/
Implement a new `--changed` flag for the maintenance agent fleet that limits checking and fixing to only files git reports as modified or new (untracked) under `devplacepy/` and `tests/`. The change introduces `agents/changed.py` with `changed_paths()` parsing `git status --porcelain`, adds `_WRITE_ALLOWLIST` guard logic in `agents/agent.py` (`set_write_allowlist`, `clear_write_allowlist`, `_allowlist_guard`) wired into `_mutation_guard` and `create_file`, threads the file list through `orchestrator.run_fleet` -> `agent.run` -> `_execute` -> `task_prompt` with a dedicated "CHANGED-FILES RUN" prompt branch, and exposes `make maintenance` (read-only) and `make maintenance-fix` (fix mode) targets in the Makefile. Documentation is updated in `AGENTS.md`, `CLAUDE.md`, and `README.md`.
This commit is contained in:
@@ -75,6 +75,25 @@ time in dependency order, so file changes never collide.
|
||||
This is the command to run before a release, or in a continuous-integration job
|
||||
(in `CHECK=1` mode it returns a non-zero exit code if anything is wrong).
|
||||
|
||||
## Checking only what you changed
|
||||
|
||||
While you work, you usually only want the fleet to look at the files you just
|
||||
touched, not the whole project. These two targets run the whole fleet but limit it
|
||||
to the files git reports as modified or new (untracked) under `devplacepy/` and
|
||||
`tests/`, so a sweep takes seconds instead of minutes:
|
||||
|
||||
```bash
|
||||
make maintenance # report only, all agents at once, just your changed files
|
||||
make maintenance-fix # fix, just your changed files
|
||||
```
|
||||
|
||||
`make maintenance` is read-only and concurrent, exactly like `make agents-all
|
||||
CHECK=1` but narrowed to your work in progress. `make maintenance-fix` fixes, and a
|
||||
built-in safety rule guarantees it can only edit the files in that changed set,
|
||||
never anything else. If nothing under `devplacepy/` or `tests/` has changed, the run
|
||||
prints "nothing to do" and exits cleanly. The same scope is available on a single
|
||||
agent with the `--changed` flag, for example `python -m agents.security --changed`.
|
||||
|
||||
## What you see while it runs
|
||||
|
||||
The output is meant to be read live, so you always know what is happening:
|
||||
|
||||
@@ -18,11 +18,11 @@ The `make` targets that produce coverage:
|
||||
|
||||
| Target | What it does |
|
||||
|--------|--------------|
|
||||
| `make coverage` | Runs the full suite single-process (`-p no:xdist`) under coverage, then `coverage combine` and `coverage report`. |
|
||||
| `make coverage` | Runs the full serial suite under coverage, then `coverage combine` and `coverage report`. |
|
||||
| `make coverage-headed` | The same, with a visible browser. |
|
||||
| `make coverage-html` | Runs `make coverage`, then `coverage html` and writes the browsable report to `htmlcov/index.html`. |
|
||||
|
||||
Coverage runs single-process on purpose: xdist is disabled so the combined profile is stable and the subprocess data merges cleanly.
|
||||
The whole suite already runs in a single process (serial execution is enforced globally in `pyproject.toml`), so `coverage combine` only has to merge two profiles: the test process and the application's Uvicorn subprocess. There are no per-worker profiles to reconcile, which keeps the combined report stable.
|
||||
|
||||
## Continuous integration: Gitea Actions
|
||||
|
||||
@@ -31,7 +31,7 @@ The pipeline is defined in `.gitea/workflows/test.yaml` and runs on every **push
|
||||
1. Check out the repository.
|
||||
2. Set up Python 3.13.
|
||||
3. Install dependencies: `pip install -e ".[dev]"` and `python -m playwright install chromium --with-deps`.
|
||||
4. Run the full suite under coverage: `python -m coverage run -m pytest tests/ -p no:xdist --tb=line`, with `PLAYWRIGHT_HEADLESS=1` and `COVERAGE_PROCESS_START` pointing at `.coveragerc`.
|
||||
4. Run the full serial suite under coverage: `python -m coverage run -m pytest tests/`, with `PLAYWRIGHT_HEADLESS=1` and `COVERAGE_PROCESS_START` pointing at `.coveragerc`. The serial flags (`--tb=line -p no:xdist`) come from `addopts` in `pyproject.toml`, so the command line stays minimal.
|
||||
5. Build the coverage report (`coverage combine`, `coverage report`, `coverage html`). This step runs with `if: always()` so a report is produced even when tests fail.
|
||||
6. Publish the `coverage-html` artifact (the `htmlcov/` directory), always.
|
||||
7. On failure only, upload the `failure-screenshots` artifact from `/tmp/devplace_test_screenshots/` (saved by the `pytest_runtest_makereport` hook in `conftest.py`).
|
||||
|
||||
@@ -13,7 +13,7 @@ All fixtures live in `tests/conftest.py`. They layer from a shared server up to
|
||||
|
||||
| Fixture | Scope | Purpose |
|
||||
|---------|-------|---------|
|
||||
| `app_server` | session | Spawns Uvicorn as a subprocess on a per-worker port with a tempfile SQLite database, then waits for it to answer. |
|
||||
| `app_server` | session | Spawns Uvicorn as a subprocess on port 10501 with a tempfile SQLite database, then waits for it to answer. |
|
||||
| `playwright_instance` / `browser` | session | One Playwright runtime and one Chromium browser for the whole run. |
|
||||
| `browser_context` | module | One shared browser context. |
|
||||
| `page` | function | Clears cookies on the shared context, then yields a fresh page. |
|
||||
@@ -22,9 +22,11 @@ All fixtures live in `tests/conftest.py`. They layer from a shared server up to
|
||||
|
||||
In-process tests skip the browser entirely and call a FastAPI `TestClient` or run coroutines through the `run_async` helper, which executes them on a worker thread and refreshes the database snapshot afterward.
|
||||
|
||||
## Isolation and ports
|
||||
## Serial execution, isolation, and ports
|
||||
|
||||
Each xdist worker runs its own server on its own port, derived from the worker index: master uses `10501`, `gw0` uses `10501`, `gw1` uses `10502`, and so on. Every worker gets a separate tempfile database. Background services are disabled in tests via `DEVPLACE_DISABLE_SERVICES=1`, and the rate limit is seeded to `1000000` so the suite is never throttled.
|
||||
The suite runs **serially, one test at a time, in a single process**. There is one server on port `10501`, backed by one tempfile database and one `DEVPLACE_DATA_DIR`, shared for the whole session. Serial execution is enforced centrally in `pyproject.toml` (`[tool.pytest.ini_options]` sets `addopts = "--tb=line -p no:xdist"`), so passing `-n` is rejected and the suite can never run concurrently. Background services are disabled in tests via `DEVPLACE_DISABLE_SERVICES=1`, and the rate limit is seeded to `1000000` so the suite is never throttled.
|
||||
|
||||
Because every test shares one server and one database, tests that mutate global or seeded state MUST clean up after themselves (see the `try/finally` rule below), and a test that changes a cached user's row through a direct database write should invalidate the auth cache (`clear_user_cache(uid)`) so the running server observes the change.
|
||||
|
||||
## Mandatory patterns
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Every test workflow has a `make` target so the commands are short and identical
|
||||
|
||||
## Setup
|
||||
|
||||
Install the package with its development extras (`pytest`, Playwright, xdist, coverage) and the browser binary once:
|
||||
Install the package with its development extras (`pytest`, Playwright, coverage) and the browser binary once:
|
||||
|
||||
```bash
|
||||
make install # pip install -e .
|
||||
@@ -17,13 +17,13 @@ python -m playwright install chromium # browser used by the E2E suite
|
||||
|
||||
| Target | What it does |
|
||||
|--------|--------------|
|
||||
| `make test` | Full suite headless, fail-fast (`-x`), `--tb=line`. The everyday command. |
|
||||
| `make test` | Full suite headless, serial (one test at a time), fail-fast (`-x`). The everyday command. |
|
||||
| `make test-headed` | The same suite in a visible browser, for watching or debugging an E2E test. |
|
||||
| `make coverage` | Full suite under coverage, single process (`-p no:xdist`), then prints a coverage report. |
|
||||
| `make coverage` | Full suite under coverage, then prints a coverage report. |
|
||||
| `make coverage-headed` | Coverage run with a visible browser. |
|
||||
| `make coverage-html` | Runs `coverage`, then writes the browsable HTML report to `htmlcov/index.html`. |
|
||||
|
||||
`make test` sets `PLAYWRIGHT_HEADLESS=1`; `make test-headed` sets it to `0`. The coverage targets disable xdist so coverage is collected in one process, which is also exactly how CI runs.
|
||||
`make test` sets `PLAYWRIGHT_HEADLESS=1`; `make test-headed` sets it to `0`. The whole suite runs in a single process: serial execution is enforced in `pyproject.toml` (`addopts = "--tb=line -p no:xdist"`), so there are no parallel workers and the coverage targets collect everything in one process, exactly how CI runs.
|
||||
|
||||
To run one test instead of the suite, call pytest directly:
|
||||
|
||||
@@ -48,5 +48,5 @@ make clean # remove __pycache__, *.pyc, the egg-info, and .venv
|
||||
|
||||
## What CI runs
|
||||
|
||||
`.gitea/workflows/test.yaml` mirrors these targets: it installs `".[dev]"` and Chromium, then runs the suite under coverage with `-p no:xdist`, publishes the coverage HTML, and on failure uploads the screenshots from `/tmp/devplace_test_screenshots/`. Running `make coverage` locally reproduces the CI result before you push.
|
||||
`.gitea/workflows/test.yaml` mirrors these targets: it installs `".[dev]"` and Chromium, then runs the serial suite under coverage (`coverage run -m pytest tests/`), publishes the coverage HTML, and on failure uploads the screenshots from `/tmp/devplace_test_screenshots/`. Running `make coverage` locally reproduces the CI result before you push.
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user