Skip to content

Language rules

What each language MUST satisfy. We do not mandate a language for a service. Shell is only glue (SHELL-1).

Go

GO-1: lint MUST pass

These are defined in the Go lint mise task.

DO NOT express per-project strictness by redefining the task.

Enforcement: //<project>:lint.

GO-2: Integration tests live in testsintegration/

  • An integration test MUST NOT run under -short.
  • You MUST use the TestMain template in the style guide.
  • test-fast MUST stay fast.

Enforcement: //<project>:test-fast behaviour. Review only otherwise.

GO-3: Depend on daedalus by module path, with replace

You MUST write require lyceum.technology/daedalus v0.0.0 plus a replace to ../daedalus_go. Module naming is in the Go style guidelines.

Enforcement: Review only.

Python

There is no separate Python style guide. These rules and ruff's configuration are it.

PYTHON-1: Every Python project MUST be a uv workspace member

You MUST list it in the root pyproject.toml [tool.uv.workspace].

A non-member cannot share the lockfile and cannot be built by the shared Python service image.

Enforcement: uv-lock (pre-commit) for the lockfile. Review only for membership.

PYTHON-2: One Python version for the whole repository

  • You MUST pin it once in the root .python-version.
  • DO NOT add a per-project pin. uv reads .python-version as an exact request.

Enforcement: //infra/docker:check-image-pins (pre-commit) for the image side. Review only otherwise.

PYTHON-3: Use the repository ruff configuration unmodified

  • lint MUST run ruff format --check and ruff check with the full configuration.
  • DO NOT hand-pick a subset.
  • Both MUST run from the repository root with the project as an explicit target. ruff resolves src against the working directory.

Enforcement: //<project>:lint, which does the cd for you.

PYTHON-4: Python MUST type-check

  • New code MUST pass the repository type checker in strict mode. The root pyproject.toml configures it.
  • app carries a baseline that MUST only shrink.
  • A cleaned file MUST NOT regress.

Enforcement: //<project>:lint runs ty, in the sweep. See enforcement gaps.

PYTHON-5: No tox

  • DO NOT add a tox environment.
  • Tasks MUST call uv directly. --only-group and the interpreter pin give the isolation.
  • tox would be a second task runner beside mise, with its own environment list to keep in step.

Enforcement: Review only.

PYTHON-6: Tests live in the project's tests/ directory

//<project>:test-fast runs tests/. It skips cleanly where there is none.

Enforcement: mise/scripts/py-test.bash.

PYTHON-7: A repository script lives in mise/scripts/

  • A script MUST be mise/scripts/<name>.py. Its tests MUST be mise/scripts/tests/test_<name>.py.
  • It MUST use the standard library only. If it needs a dependency, then it belongs in a workspace package such as neph.
  • It MUST run without Mise as uv run --no-project python mise/scripts/<name>.py. Its Mise task runs that.
  • It MUST import shared code from mise/scripts/lib.py as a sibling module: import lib.
  • It MUST run every command through lib.run. A test replaces that one function.

mise/scripts/ is the lyceum-mise-scripts workspace member and a Mise config root. It is never built.

Enforcement: //mise/scripts:lint and //mise/scripts:test-fast, in the sweep (check-lint-and-test.yml). Review only for the rest.

Shell

Shell is glue. A script is Python.

SHELL-1: Shell is glue. A script MUST be Python

  • Shell glue MUST be at most a few lines of simple commands, with at most one control statement.
  • Glue MAY pipe through simple filters such as grep -F, cut, sort and head.
  • Anything more is a script. A script MUST be Python (PYTHON-7).
  • Complicated sed, awk, jq or yq MUST NOT be used in shell.
  • A regular expression MUST NOT be used in shell. A pattern MUST be a glob or a fixed string. A regular expression in shell cannot be unit-tested.
  • Shell MUST NOT hold data structures, arithmetic or error recovery.
  • This binds every shell file, every workflow run: step and every Mise task run.
  • An existing shell script MAY take a small fix. A change that adds logic to it MUST port it to Python.

Enforcement: Review only.

SHELL-2: Every shell file names its interpreter and passes shellcheck

  • A file MUST be .bash or .sh, with set -euo pipefail at the top.
  • It MUST be clean under the repository .shellcheckrc.
  • A sourced file keeps its shebang and exec bit, so check-shebang-scripts-are-executable passes. mise/scripts/go-env.bash is the model.

Enforcement: //mise:lint for mise/scripts/. Review only elsewhere. See enforcement gaps.

SHELL-3: Scripts MUST be runnable without Mise

  • You MUST configure a script by environment variables with documented defaults.
  • A Docker stage MUST be able to call one directly.

Enforcement: Review only.

SHELL-4: Avoid the four shell traps

  • DO end a function whose last statement is a conditional with return 0. Under set -e a false if aborts the caller.
  • DO add || true to a grep in a command substitution where you handle the empty case yourself. Under pipefail a non-match aborts the script.
  • DO NOT use sed -i. Use sed_inplace from mise/scripts/lib.bash. GNU and BSD spell -i differently.
  • DO check a tool's docs before claiming a variable name, and prefix ours. Docker reads DOCKER_CONTEXT itself.

Enforcement: shellcheck, for some. Review only for the rest.

SHELL-5: Every shell script MUST be compatible with both Linux and MacOS

  • You MUST write shell scripts compatible with both Linux and MacOS.

Enforcement: Review only.