Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Developer tooling

This page applies to the public Maison framework repository. Maison uses dstack/Copier controls and Beads for project lifecycle work. Consumer repositories are plain Git configuration roots: they own their flake, lock, inventory, host configuration, mise policy, and dotfiles, but do not need to install Maison’s contributor workflow.

The repository uses mise to provide project tools and named tasks. Install mise, then run:

mise install --locked

The project toolchain includes Python 3.13.14. Bootstrap installs and activates that locked runtime with mise exec --locked python, so its Python helpers do not depend on the host Python installation. Use the same explicit runtime for direct Python commands:

mise exec --locked python -- python -m unittest -v tests.test_inventory_behavior

Use the same commands locally and in automation:

mise run check
mise run check:tests
mise exec --locked python -- python -m unittest -v tests.test_inventory_behavior
mise exec --locked python -- python -m unittest -v tests.test_deployment_contracts
mise exec --locked python -- python -m unittest -v tests.test_migration_behavior
mise exec --locked python -- python -m unittest -v tests.test_ownership_boundary
mise exec --locked python -- python -m unittest -v tests.test_repository_contracts tests.test_repository_mutation
mise exec --locked python -- python -m unittest -v tests.test_transaction_behavior
mise run fix
mise run docs:check
mise run docs:build
mise run docs:deployment:enable
mise run docs:serve

Use /update-project for the recorded template channel, or /update-project --stable / --unstable to change it. The update always records the exact resolved template commit.

check is read-only. fix changes the working tree. Contextlint checks links, anchors, and image targets across README and docs/**/*.md. The pre-commit hook may fix files while safely stashing unrelated unstaged work. The commit-message hook enforces Conventional Commits, required scopes for changelog-visible changes, grammar, 72/100-character line limits, and canonical optional Beads: footers. Harper applies its full native rule set to human-authored text after filtering Git comments/diffs, canonical release subjects, and the canonical machine-readable footer. Run cog changelog to preview the concise user-facing changelog. The hk policy uses native built-in steps whenever their behavior matches; hk’s file locking coordinates independent steps. No dependency chain serializes unrelated checks. Go projects retain two output-sensitive edges: gofumpt follows goimports, then fix-only module tidy observes the final imports.

No recognized language profile is active; only the universal tooling baseline runs.

Pi extensions are personal consumer content and are maintained in the private Terroir repository. Maison does not install or validate those runtime extensions.

The Python regression suite is split by subsystem. check:tests runs unittest discovery across every tests/test_*.py file and is expected to complete within five minutes on a warm supported development checkout. Use the focused commands above while editing a subsystem, then run mise -E dev run check once before committing broad or shared test changes.

Tests that execute external commands use the shared helper in tests/support/processes.py. It defaults to a 30 second timeout, starts commands in an isolated process group where supported, terminates the group on timeout, captures stdout and stderr, and truncates failure diagnostics. Longer timeouts must be explicit, documented at the call site, and no longer than 300 seconds.

Maison’s TOML mutation helper uses a pinned tomlkit wheel checked in under .mise/vendor/. The helper verifies the wheel digest before importing it, so configuration repair commands still work when normal mise project tool resolution is skipped or broken and do not depend on ambient Python site-packages.

GitHub validation

.github/workflows/ci.yml runs on every push to main and pull request. Its repository-checks job installs Nix, isolates user-global mise configuration, installs the committed tools, and runs mise run check. The same workflow also builds supported platform outputs and exercises the pre-mise bootstrap boundary.

.github/workflows/cache-refresh.yml is dependency-update automation, not an approval path. It may refresh flake.lock, build cache targets, push or update the automation/refresh-flake-lock branch, open or edit the matching pull request, and run the reusable hk and CI workflows against that branch. It must leave the pull request open for ordinary review and branch protection; it must not run gh pr merge, use --admin, or enable auto-merge.

The consumer is selected locally with --consumer during bootstrap, MAISON_CONSUMER_ROOT, or the current consumer Git checkout. Consumer files and locks are ordinary Git state and are never copied into Maison. Do not commit consumer contents or Bitwarden material to Maison while running development checks.

Consumer validation

Consumers can run the Maison-owned contract without adding a second test suite:

maison consumer validate --consumer /path/to/consumer

The read-only check validates the consumer flake and lock, inventory, supported systems, mise package and dotfile configuration, fnox references, documentation links, and raw credential/private-key boundaries. It evaluates expected Nix outputs without activation or lock updates, and the validator itself never invokes fnox providers; provider credentials are therefore not needed by the validation contract in CI. maison self update uses this same command through the candidate Maison package after changing only the consumer’s Maison input, so failed framework upgrades can restore the prior lock and local CLI state without touching Maison’s own checkout lock.

Hooks and recovery

Setup installs repository-local hk hooks when the destination is a Git repository. To restore tooling after an offline or degraded setup, run:

python3 scripts/setup-tooling.py --json

The command gives lock, install, and hook stages one temporary MISE_CONFIG_DIR, removes inherited global config overrides, and deletes the temporary directory on exit. It preserves the scaffold on failure, reports the failed stage, and uses the same command above for recovery. A repository created without Git can install hooks after Git initialization with:

python3 scripts/setup-tooling.py --json