|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +Orientation for Claude Code sessions in this repo. The `README.md` covers what the project is and how to use it; this file covers things that aren't obvious from reading the code. |
| 4 | + |
| 5 | +## Spec is the source of truth |
| 6 | + |
| 7 | +This repo is a Python implementation of [`openarmature-spec`](https://github.com/LunarCommand/openarmature-spec). Behavior is defined by the spec; this repo executes it. |
| 8 | + |
| 9 | +- The spec lives at `openarmature-spec/` as a git submodule pinned to a released tag. Don't edit files in the submodule. |
| 10 | +- To bump the spec: `cd openarmature-spec && git checkout <tag>`, then bump the three places that track the spec version (see below). |
| 11 | +- Behavior changes that aren't already in the spec require a proposal in the spec repo first, not a PR here. |
| 12 | + |
| 13 | +## Three places hold the spec version — keep them in sync |
| 14 | + |
| 15 | +- `tool.openarmature.spec_version` in `pyproject.toml` |
| 16 | +- `__spec_version__` in `src/openarmature/__init__.py` |
| 17 | +- The submodule commit (must match a released tag, e.g. `v0.1.1`) |
| 18 | + |
| 19 | +`tests/test_smoke.py` asserts the first two match. The third is enforced by convention. |
| 20 | + |
| 21 | +## Test layout |
| 22 | + |
| 23 | +- `tests/conformance/` — runs the spec's YAML fixtures against the engine via an adapter. Drives most of the behavior coverage. |
| 24 | +- `tests/unit/` — fills coverage gaps the conformance suite doesn't reach: `edge_exception`, `reducer_error`, `state_validation_error`, `SubgraphNode.run`, projection variants, frozen-state mutation, etc. |
| 25 | +- `tests/test_smoke.py` — version sync. |
| 26 | + |
| 27 | +## Tooling |
| 28 | + |
| 29 | +- `uv` for everything. Don't use `pip` directly. |
| 30 | +- Pyright **strict mode** is enforced (`pyproject.toml`). Annotations are not optional. |
| 31 | +- Ruff for lint + format. Pre-commit hook runs `ruff format` automatically — the file you committed may not be the file in the next diff. |
| 32 | +- `pytest-asyncio` with `asyncio_mode = "auto"` — `async def test_...` works with no decorator. |
| 33 | + |
| 34 | +## Common commands |
| 35 | + |
| 36 | +```bash |
| 37 | +uv run pytest -q # all tests |
| 38 | +uv run pytest tests/conformance/ -v # spec conformance only |
| 39 | +uv run ruff check . && uv run ruff format # lint + format |
| 40 | +uv run pyright src/ tests/ # type check |
| 41 | +``` |
| 42 | + |
| 43 | +## Engine design notes that are easy to miss |
| 44 | + |
| 45 | +- `State` is `frozen=True` AND `extra="forbid"`. Nodes that return an undeclared field surface as a `state_validation_error`, not a silent drop. |
| 46 | +- Conditional edges over-approximate at compile time (a conditional from node X is treated as reaching every node), so the unreachable-node check is sound but not tight. |
| 47 | +- Each node has exactly one outgoing edge. Branching is via conditional edges, not multiple statics. |
| 48 | +- `END` is a distinct sentinel object, not a reserved string. Use the exported `END` constant. |
0 commit comments