# Contributing Thanks for helping with OTKO — a SAP2000-style desktop GUI for OpenSeesPy. Early-stage project: the bar is architecture cleanliness, not feature count. If your change breaks a layering rule below, it won't merge — no matter how useful the feature. ## Dev setup ```bash python -m venv .venv source .venv/bin/activate # Linux / macOS # .venv\Scripts\activate # Windows pip install -e ".[gui,dev]" pre-commit install ``` `pip install -e ".[gui,dev]"` pulls the Qt/PyVista desktop stack plus the dev tools. For a headless checkout (core + services only, no Qt) use `pip install -e .` instead. Python 3.10+; on Windows use 3.12+. Launch the GUI with: ```bash python -m otko ``` `pre-commit install` wires ruff + mypy into your local git hooks so obvious issues are caught before a commit. Run it once per clone. ## Before opening a PR Run the verify commands in this order and make sure they are all clean: ```bash ruff check src tests ruff format src tests # line-length 100, E501 ignored mypy src/otko/core src/otko/services pytest -m "not slow" # CI gate: lint → this, on 3.10/3.11/3.12 × ubuntu/windows/macos ``` Focused runs are faster while iterating: `pytest tests/unit` (pure logic, milliseconds), `pytest tests/gui -k ` (pytest-qt, needs a display; Linux GUI tests want `xvfb-run -a pytest ...`), and `pytest tests/integration -k ` (real OpenSeesPy runs). Single test: `pytest tests/unit/test_project.py::test_name -q`. Markers: `gui`, `slow`. ## Architectural rules (enforced in review) OTKO uses a strict one-way **MVVM + service** architecture. Dependencies flow outward-in only: ``` views → viewmodels → services → core ``` `commands` sits alongside the bridge and owns every model mutation. 1. `core/` is pure Python — stdlib + numpy + pydantic. It may **not** import Qt or `openseespy`. Period. 2. `services/` may use `core` + `h5py` + `openseespy`, but may **not** import Qt. 3. `views/` (PySide6/pyvistaqt) may **not** import `openseespy` directly — go through a service. No business logic in `views`. 4. `viewmodels/` bridges `core` ↔ Qt (signals, `QUndoStack`). 5. `commands/` holds the `QUndoCommand` subclasses; all model mutations go through `commands`, not ad-hoc edits in `views`. 6. Public functions and methods need type hints and a docstring. 7. New domain entities go through Pydantic validation. 8. Long-running operations (>50 ms) run off the GUI thread (`AnalysisWorker` in a `QThread`, cancelled via `isInterruptionRequested()`; results cross threads as a lightweight `ResultsHandle` written to HDF5). The full package map and the fixed OpenSeesPy command order the runner emits live in [`docs/architecture.md`](docs/architecture.md). Never reorder the runner's commands. ## Branch model `main` is the default and integration branch. Work on short-lived topic branches cut from `main` — `feat/`, `fix/`, or `docs/` — and open a pull request against `main`. Do not commit directly to `main` from an agent session. There is no `develop` branch. ## Commit style Conventional Commits — `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`, `ci:`. Keep each commit focused; a `feat:` commit should add a feature, not mix one in with unrelated refactors. ## Documentation If your change is user-visible, update [`docs/QUICK_GUIDE.md`](docs/QUICK_GUIDE.md) and the relevant roadmap or ADR entry. Project model files are `.osmodel` (Pydantic-validated JSON); regenerate the checked-in `examples/*.osmodel` from their scripts with `python examples/.py` rather than hand-editing them.