RimSort is a free and open-source multi-platform mod manager and sorter for RimWorld. It reads RimWorld About.xml metadata, sorts mod lists using graph/topological algorithms plus community/user rules databases, and integrates with the Steam Workshop (via SteamworksPy and SteamCMD), Git mods, and optional static metadata databases (SteamDB, Community Rules).
- Stack: Python
==3.12.*(pinned inpyproject.toml), PySide6 6.11 / Qt6 GUI, Nuitka 4.x packaging, uv for deps, just task runner. Targets Windows, macOS, and Linux — no platform-locked code without a guard. - Architecture: pragmatic MVC under
app/(views= PySide6 widgets,controllers= glue/logic,models= data classes,services= reusable operations,utils= helpers,sort= ordering algorithms,windows= top-level dialogs,cli=build-dbetc.). Tests intests/mirror theapp/layout.
- Clone with submodules:
git clone --recurse-submodules https://github.com/RimSort/RimSort - If submodules are missing:
git submodule update --init --recursive - Prerequisites: git, Python 3.12, uv, just. Full
just checkalso needs Node/npx (jscpd, markdownlint) and shfmt. just dev-setup— creates venv,uv sync --locked --dev --group build, compiles locales. Do this first.just install-hooks— installs the pre-commit hook (runsjust checkbefore every commit; requiresjustandpwshon PATH).- Always use the local venv via
uv run ...— never activate the venv manually or use a barepytest/ruff. - Claude Code reads
CLAUDE.md, notAGENTS.md— bridge it with@AGENTS.mdat the top ofCLAUDE.mdor a symlink (Windows symlinks need Developer Mode).
Never run uv lock --upgrade (== just update) as part of a task — dependency bumps are handled by dependabot.
just run(==uv run python -m app)- Use dev mode so you don't touch real user data:
uv run python -m app --dev— redirects settings/logs/databases/mod-lists todev/(gitignored) and enables debug logging. Env overrides:RIMSORT_DEV,RIMSORT_DEV_DIR.
- Full suite:
just test(==uv run pytest --doctest-modules --no-qt-log -s) - Verbose:
just test-verbose; with coverage:just test-coverage - Single file:
uv run pytest --doctest-modules --no-qt-log tests/views/test_mods_panel_search.py -s - Single test: append
-k "name_or_substring"to the above. - Never run bare
pytest— the project'saddoptsmanage--import-mode=importlib,pythonpath, andtestpaths. - Qt tests use the
qtbotfixture (pytest-qt);qapp,mock_app_info,fresh_event_bus,mock_metadata_controller,mock_steamcmd_interfaceare defined intests/conftest.py. Tests must never launch real Steam URLs or dialogs —tests/conftest.pyautouse fixtures block both. - Add or update tests for any logic you change.
just check runs the quality gate (Windows: typecheck, pyright, ruff, ruff-format, jscpd, markdownlint, shfmt, deferred-imports; Unix adds super-linter, which covers the same plus bash, json, yaml, github-actions, checkov, and gitleaks). just fix auto-fixes ruff check/format, shfmt, and markdownlint.
| Command | Purpose |
|---|---|
just ruff |
Ruff lint check (ruff check, config in pyproject.toml) |
just ruff-format |
Ruff format check (ruff format --check) |
just ruff-fix / just ruff-format-fix |
Ruff lint/format auto-fix (config in pyproject.toml) |
just typecheck |
mypy against pyproject.toml |
just pyright |
pyright (standard mode) against pyproject.toml |
just jscpd |
Copy-paste detection — CI enforces 0% duplication (config in .jscpd.json). If code repeats, extract a shared helper. |
just deferred-imports |
Guard against new function-local from app… imports (see Traps) |
just markdownlint |
Markdown lint for docs/** and root *.md (rules in .markdownlint.json; options in .markdownlint-cli2.jsonc) |
just shfmt |
Shell script formatting (fails when any script would be reformatted) |
Run at minimum just fix + just test + just typecheck + just pyright (or the full just check, then just test) and get everything green before declaring a change complete. CI runs ruff, ruff-format, mypy, pyright, jscpd, gitleaks, markdownlint, and pytest on ubuntu/macos/windows.
- Ruff (config in
pyproject.toml): line-length 88, indent 4,quote-style = "double", isort (I) enabled;E402andBLE001are intentionally ignored project-wide — do not re-enable, and do not add project-wide# noqafor other rules. - Type annotations required on all function/method signatures (mypy enforces
disallow_untyped_defs,disallow_untyped_calls, etc.). - Use modern Python: PEP 604 unions (
str | None), built-in generics (list[str]),collections.abc— avoid importing fromtyping. NeverOptional[str], neverfrom typing import .... - Docstrings: Sphinx reST format (Google-style accepted in tests). Example:
def sync(force: bool) -> None: """Run a full sync.\n\n:param force: Ignore caches.\n""" sys.platform == "win32"-only code must be platform-guarded; the codebase targets Windows, macOS, and Linux.- All user-facing strings must go through Qt translations (see i18n below). Log and exception messages stay in English.
- Prefer type-explicit, readable logic over clever one-liners.
- Every user-facing string must use
QCoreApplication.translate("ContextName", "English source string")— the context is normally the class name (e.g."ModsPanel","InstanceService"). Some modules aliastranslate = QCoreApplication.translate; follow the local convention. - After adding/changing translatable strings: run
just i18n-update(runspyside6-lupdateto merge intolocales/*.ts, source language isen_US), thenjust i18n-compile(regenerates.qm). As a contributor, commit both the.tsand.qmchanges. translation_helper.pyis the CLI tool for translation workflows (check/completeness, validate, auto-translate). Only edit.tsfiles;.qmare generated artifacts.- Preserve
%s,{0},\n, and HTML placeholders exactly in any translated string you touch. - Do not change translation text of existing strings unless the English source changed.
app/— application source (Python package).tests/— pytest suite mirroringapp/. Fixtures live intests/conftest.py; test data intests/data/.locales/— Qt.ts(source) +.qm(compiled) translation files.themes/— Qt stylesheet themes and default icons.docs/— Jekyll documentation site source (development-guide/,user-guide/). Doc pages use Jekyll frontmatter (title,layout: default,parent,permalink).packaging/— release packaging (AppImage, MSI, macOS bundle optimize).todds/— texture-optimization binary location (runtime only).libs/,submodules/— vendor/extras, see Boundaries.app/utils/github/— GitHub provider, models, installer, updater;app/utils/steam/— Steam integration (steam/workshop/,steam/steamcmd/).distribute.py— release build driver;rimsort.nuitka-package.config.ymland header comments inapp/__main__.pyhold Nuitka config.
- Feature-specific PRs only — one PR = one change. Every PR must reference a corresponding issue (or sub-task).
- Conventional Commits, lowercase prefix:
feat:,fix:,docs:,chore:,i18n:,build(deps):,build(deps-dev):. Do not include a scope unless it's meaningful (e.g.build(deps)for bump PRs). - Do not submit dependency-bump PRs — dependabot and maintainers handle them.
- Semantic versioning is automated from keywords in commit messages:
(major)for breaking,(minor)for features; the rest are implicit patches. Version only monitorsapp/,libs/,submodules/,themes/. - Tests + all quality checks must pass and the PR should be ready before requesting review; use a draft otherwise.
- 🚫 Never — edit
submodules/(vendored: SteamworksPy, steamfiles) or the prebuilt Steamworks binaries inlibs/; submit dep-bump PRs; commit secrets/API keys (name env variables only),.venv,dev/,build/,dist/,version.xml(generated, gitignored). ⚠️ Ask first — changing public/__init__.pyAPI signatures, adding a runtime dependency, editingdistribute.pyor Nuitka config, running a release/build/publish workflow, bumping dependencies, or doing large refactors beyond the task.- ✅ Always — use
uv run ...for everything, add/update tests for changed logic, run the Definition of Done checks below before declaring done. - Avoid importing Qt-heavy modules at module scope where it creates cycles/startup cost — see
check_deferred_imports.py(Traps). - Do not run
just update,just build, or release workflows as part of normal tasks.
just fix+just typecheck+just pyright+just deferred-importsall pass (or fulljust check).just testpasses (full suite).- The diff contains no unrelated reformatting, comments, or scope creep.
- New translatable strings have been run through
just i18n-updateandjust i18n-compile. (Note: the i18n rule in the AGENTS.md spec — run these and commit both.ts/.qm.)
- Deferred imports are judged, not banned:
check_deferred_imports.pyfails on any new function-localimport app…not in itsALLOWEDset. Genuine circular-import fixes go inALLOWED; start-up hot-path imports may stay deferred. If you add one, you must updateALLOWEDor CI (just deferred-imports) fails. - Never reformat code wholesale or add comments explaining what the code does — match surrounding style and only change what your feature touches.
- Don't create new top-level data dirs/files under the repo root; app data goes through
AppInfo(which tests redirect viamock_app_info). - Keep
docs/edits lint-clean (markdownlint) and only touchdocs/index.md/locale variants when truly needed.