@@ -77,6 +77,16 @@ current event.
7777- ` on_error_execution() ` works via naming convention but ** only** when a transition for
7878 ` error.execution ` is declared — it is NOT a generic callback.
7979
80+ ### Thread safety
81+
82+ - The sync engine is ** thread-safe** : multiple threads can send events to the same SM instance
83+ concurrently. The processing loop uses a ` threading.Lock ` so at most one thread executes
84+ transitions at a time. Event queues use ` PriorityQueue ` (stdlib, thread-safe).
85+ - ** Do not replace ` PriorityQueue ` ** with non-thread-safe alternatives (e.g., ` collections.deque ` ,
86+ plain ` list ` ) — this would break concurrent access guarantees.
87+ - Stress tests in ` tests/test_threading.py::TestThreadSafety ` exercise real contention with
88+ barriers and multiple sender threads. Any change to queue or locking internals must pass these.
89+
8090### Invoke (` <invoke> ` )
8191
8292- ` invoke.py ` — ` InvokeManager ` on the engine manages the lifecycle: ` mark_for_invoke() ` ,
@@ -127,6 +137,16 @@ timeout 120 uv run pytest -n 4
127137
128138Testes normally run under 60s (~ 40s on average), so take a closer look if they take longer, it can be a regression.
129139
140+ ### Debug logging
141+
142+ ` log_cli_level ` defaults to ` WARNING ` in ` pyproject.toml ` . The engine caches a no-op
143+ for ` logger.debug ` at init time — running tests with ` DEBUG ` would bypass this
144+ optimization and inflate benchmark numbers. To enable debug logs for a specific run:
145+
146+ ``` bash
147+ uv run pytest -o log_cli_level=DEBUG tests/test_something.py
148+ ```
149+
130150When analyzing warnings or extensive output, run the tests ** once** saving the output to a file
131151(` > /tmp/pytest-output.txt 2>&1 ` ), then analyze the file — instead of running the suite
132152repeatedly with different greps.
@@ -160,6 +180,26 @@ async def test_something(self, sm_runner):
160180
161181Do ** not** manually add async no-op listeners or duplicate test classes — prefer ` sm_runner ` .
162182
183+ ### TDD and coverage requirements
184+
185+ Follow a ** test-driven development** approach: tests are not an afterthought — they are a
186+ first-class requirement that must be part of every implementation plan.
187+
188+ - ** Planning phase:** every plan must include test tasks as explicit steps, not a final
189+ "add tests" bullet. Identify what needs to be tested (new branches, edge cases, error
190+ paths) while designing the implementation.
191+ - ** 100% branch coverage is mandatory.** The pre-commit hook enforces ` --cov-fail-under=100 `
192+ with branch coverage enabled. Code that drops coverage will not pass CI.
193+ - ** Verify coverage before committing:** after writing tests, run coverage on the affected
194+ modules and check for missing lines/branches:
195+ ``` bash
196+ timeout 120 uv run pytest tests/< test_file> .py --cov=statemachine.< module> --cov-report=term-missing --cov-branch
197+ ```
198+ - ** Use pytest fixtures** (` tmp_path ` , ` monkeypatch ` , etc.) — never hardcode paths or
199+ use mutable global state when a fixture exists.
200+ - ** Unreachable defensive branches** (e.g., ` if ` guards that can never be True given the
201+ type system) may be marked with ` pragma: no cover ` , but prefer writing a test first.
202+
163203## Linting and formatting
164204
165205``` bash
0 commit comments