Thanks for your interest. This file documents the project conventions
referenced from README.md.
git clone https://github.com/simtabi/shimkit
cd shimkit
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest -q
ruff check src tests
mypy src/shimkit
bandit -r src/shimkit -ll
pip-audit --skip-editableCI runs the same gates on macOS + Ubuntu × Python 3.10/3.11/3.12/3.13. All must be green on every PR.
The same gates run pre-commit so PRs land without surprises:
pip install pre-commit
pre-commit installThe default hooks run ruff, ruff-format, shellcheck, file-format
checks, and trailing-whitespace cleanup. mypy is gated to the
manual stage (it's too slow for every commit); run it explicitly:
pre-commit run --hook-stage manual mypyEach new tool has an optional dependency extra so the base install stays lean. To work on a specific tool, install its extra:
pip install -e ".[dev,adguard]" # ruamel.yaml + requests + psutil
pip install -e ".[dev,docker-clean]" # docker SDK
pip install -e ".[dev,extra-tools]" # everythingThese are load-bearing — break them and a future tool will diverge from the rest of the kit in ways that are hard to recover from.
-
Every tool builds on
shimkit.coreprimitives. Do not re-implement subprocess execution, OS detection, package-manager dispatch, shell detection, rc-file writing, terminal output, or interactive prompts. Ifshimkit.coreis missing what you need, extend it — don't fork it inside a tool. -
CommandRunner.runis the only place that callssubprocess. This is the audit chokepoint. If you find yourself reaching forsubprocessdirectly, route it throughCommandRunnerinstead. Tests mock at this layer. -
Config values come from
shimkit.config.get_config(). Class-level constants for user-facing data (supported versions, search paths, command templates, colour toggles, repo URLs) belong insrc/shimkit/config/defaults.jsonand the pydantic schema. Add the field toschema.py, populatedefaults.json, regenerateconfig/shimkit.schema.json, and read it fromget_config(). -
Logic-critical strings stay in code. Idempotency markers (e.g.
# java-manager:openjdk@21), regex patterns, and atomic-replace semantics must NOT be config-driven — a user changing the marker would corrupt their existing rc files. The line: if changing the value could break existing on-disk state, it's not config. -
Builder pattern for orchestrators. Every tool's top-level class follows
Tool.create().boot().run().boot()wires components and returnsself;run()is the interactive menu. Non-interactive subcommands call methods likeinstall(version),list_things()— never the menu. -
Fluent contracts return
self. Methods likeShell.ensure_config_exists(),ShellConfigWriter.write_java_env(),UI.success()(etc.) return their receiver so callers can chain.
- Create
src/shimkit/tools/<toolname>/with__init__.py,models.py, the tool's domain classes,manager.pyfor the orchestrator, andcommands.pyfor the Typer surface. - Add a section under
toolsinsrc/shimkit/config/schema.pyand defaults insrc/shimkit/config/defaults.json. Regenerateconfig/shimkit.schema.json. - Wire
app.add_typer(<toolname>_app)insrc/shimkit/cli.py. - Add
tests/test_tools_<toolname>.pywith at least: aManager.bootsmoke test, a CLI help test, a CLI exit-code propagation test. - Update
README.mdto add the new subcommand to the Tools section and the help block at the top.
A tool joins shimkit only if it shares ≥2 of Platform / Shell /
PackageManager / UI / Menu. Otherwise it's a separate package.
tests/conftest.pyprovides an autouse_reset_env_and_config_cachefixture and arunner: CliRunnerfixture. Use them rather than re-rolling.- For env or filesystem state:
monkeypatch.setenv/monkeypatch.delenv/tmp_path. Tests must be hermetic against the developer's environment. - For subprocess: stub
shimkit.core.CommandRunner.runrather than reaching intosubprocess. Tests that hitsubprocessdirectly through a leaf module are signal that the module is bypassing the chokepoint. - For pydantic config: write a JSON file under
tmp_path, setSHIMKIT_CONFIG=<path>, thenreset_cache(). The autouse fixture resets between tests so leakage isn't a concern.
See docs/release.md. Releases are
tag-driven; the guard job in release.yml will fail if the git tag
doesn't match pyproject.toml's project.version.
ruff checkis authoritative. Don't argue with it — adjust the code.mypy --strictmust pass. The pydantic plugin is configured; if you're hitting "Returning Any" errors, narrow the return at the source rather than# type: ignore-ing the call site.- No comments that explain what the code does. Names should do that.
Comments are for why — non-obvious constraints, workarounds, or
references to issues.
# Phase X says…migration references must not survive into committed code. - Default to no error-handling/fallback for cases that can't happen.
Only validate at system boundaries.
CommandRunneralready catches everything from subprocess; trust it.