Project-specific instructions for the machine repo. Global agent
instructions live in agents/AGENTS.md. Do not duplicate them here.
- Declarative first: Express desired state in
mise.toml, fall back to - Single manifest:
mise.tomlat the repo root is the global config - Minimalism: Standard mise features > custom scripts.
mise.toml: Source of truth for tools, packages, dotfiles, services,env.sh: Permanent. Single source of truth for environment variables.tasks/: Shell scripts for imperative setup that can't be declarative- Topic directories (
fish/,neovim/,ghostty/, etc.): Config source - Nested
mise.toml:llama/,whisper/,keys/,kwin/,endpoint-verification/— local tasks viamise run -C <dir> <task>
- Bootstrap:
mise bootstrap --yesprovisions the full machine. - Fresh machine:
- Status check:
mise bootstrap status --missing - Execution plan:
mise bootstrap plan - Dry run:
mise bootstrap --dry-run
Start with mise run discover. Before probing anything it refuses to run while
any PR is open on the remote: the bump work may already be half-done in that PR,
from this machine or another. When it aborts, list the PRs with gh pr list, then
merge or close them. Pull the result before rerunning:
jj git fetch --remote origin && jj rebase -o main@origin.
The task then prints one row per pinned thing with the
pinned version, the latest upstream release, and ok / BUMP / ?, then runs
mise outdated for [tools]. It reads GitHub through gh api, so gh auth status must show a login. A ? means the probe matched nothing, which usually
means upstream changed its release scheme: fix the probe in tasks/discover.sh
before trusting the row.
- Desktop apps (Slack, Zoom, Cursor, Yaak, SoapUI, Obsidian): edit
[vars]inmise.toml. Nothing else holds an app version. Cursor needscursor_hashbumped withcursor_version; both values come from the samediscoverrows (seecursor/AGENTS.md). Fonts usebrew-cask:font-*under[bootstrap.packages]and update via package bootstrap. - mise tools:
[tools]useslatest(runtimes keep a major prefix:node = "24",python = "3.12",go = "1.26"). Exact versions live inmise.lock.mise upgradeinstalls newer matches and rewrites the lockfile. Do not usemise upgrade --bump(that would changenode = "24"to"26").mise lock --globalneedsGITHUB_TOKEN(e.g.gh auth token) or GitHub rate-limits leave platforms missing. Commitmise.lock. After installs,mise reshim. - GPU services (llama, whisper): update
versionin[vars]in the topicmise.toml. The setup scripts read it from there (single source). Both pin the newest non-prerelease semver tag, not abNNNNnightly, and the setup scripts add thevprefix because GitHub source tarballs drop it. Both repos also publishbNNNNbuild tags, and/releases/latesthas pointed at one of those, sodiscoverfilters on release shape instead. After a bump, restart the user services: the setup scripts rewrite the units but do not restart an already-running service. - endpoint-verification:
mise run -C endpoint-verification discoverprintsversion,deb, andsha256as a paste-ready[vars]block. All three move together: the filename carries a per-build hash, and install verifies the checksum. - Obsidian: some tags ship an Android APK only.
discoverreports the newest release that has a desktop AppImage, so trust that row over the tag.
Editing [vars] changes nothing on disk. Run these in order.
mise run applyconverges packages, services, dotfiles, tools, and desktop apps. This command must always run and succeed before you finish a bump. If it stops oncodex:checkorantigravity:check:- Inspect the diff first.
- Integrate any intentional config changes (model, effort, preferences) back into the repo config. Never keep workspace paths or runtime state.
- Run the resolve task (
mise run codex:resolveormise run antigravity:resolve). - Re-run
mise run applyuntil it succeeds.
- Restart any GPU service whose version moved:
systemctl --user restart llama-server whisper-server. mise run cleanlast, never first. It deletes the source tree the running service still points at. Old versions accumulate under~/.local/share/odsod/machine/dataand asllama.cpp-*/whisper.cpp-*source trees, and it keeps only the pinned ones.- Verify:
mise run discovershows every rowok,mise run applysucceeds with zero errors,mise bootstrap --dry-runreports no work, and each service answers on its port, for examplecurl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8179/health.
If mise bootstrap stops with "refusing to overwrite existing files", an app
has replaced its dotfile symlink with a real file. Copy the live file into the
repo before using --force-dotfiles, or its runtime edits are lost.
After pulling commits that changed mise.toml or mise.lock:
mise run apply
Runs full mise bootstrap --yes (packages, services, files, dotfiles,
tools, desktop apps) then regenerates shims.
mise is bootstrapped via COPR dnf package, then self-managed via [tools].
- Check latest:
mise self-update --dry-runor GitHub releases - Read release notes for all versions since current:
defuddle parse https://github.com/jdx/mise/releases/tag/v<version> --md - Evaluate each new feature against the current config:
- Does it replace something imperative in our tasks/?
- Does it simplify an existing section?
- Does it enable removing a hook or script?
mise upgrade aqua:jdx/miseupdatesmise.lock; do not pin a version in[tools]("aqua:jdx/mise" = "latest")- Apply any config simplifications identified in step 3
| What | Where |
|---|---|
| New dev tool binary | mise use <tool> (adds to [tools]) |
| New system package | Add "dnf:<pkg>" = "latest" to [bootstrap.packages] |
| New font | Add "brew-cask:font-<name>" = "latest" to [bootstrap.packages] |
| New config symlink | Add entry to [dotfiles] |
| New env var | Add to env.sh (picked up by mise, KDE, systemd) |
| New systemd system service | Add to [bootstrap.services] |
| New privileged file | Add to [bootstrap.files] |
| Complex imperative setup | Add script to tasks/, wire into [tasks] |
| New pinned desktop app | Add <app>_version to [vars], probe in discover |
| Section | Purpose |
|---|---|
[settings] |
Mise behavior config |
[vars] |
Pinned desktop app versions |
[tools] |
Versioned dev tool binaries |
[env] |
Sources env.sh via _.source, sets repo |
[settings.dotfiles] |
Dotfiles root config |
[dotfiles] |
Config symlinks and inline dotfiles |
[bootstrap.packages] |
dnf, brew-cask (fonts), flatpak packages |
[bootstrap.hooks.*] |
Pre/post hooks for packages and tools |
[bootstrap.files] |
Privileged files (sudoers, sshd, xkb) |
[bootstrap.groups] |
System group declarations |
[bootstrap.users] |
System user accounts and group memberships |
[bootstrap.services] |
System services (sshd, docker, tailscaled, odsod-keys) |
[tasks.*] |
Imperative setup scripts |
env.shis permanent: KDE and systemd don't run mise. They need env[bootstrap.linux.systemd.units]createsdev.mise.*units. Can't[bootstrap.services]is system-level only (not--user).[bootstrap.files]needsreplace = trueto convert existing symlinksgithub:backend for GitHub release tools (replaces deprecatedubi:).- Fish login shell uses a stable symlink at
/usr/local/bin/fishpointing - Neovim as vim/vi: Remove vim-enhanced/vim-minimal RPMs, symlink in
- Fonts:
brew-cask:font-*installs directly to~/.local/share/fontswithout Homebrew. - Apps that rewrite their own config: Antigravity writes
modelandtrustedWorkspacesinto its settings at runtime, and Cursor's livecli-config.jsonholdsauthInfoand auth cache keys; none of that may reach this public repo. Their live files are plain files, not symlinks.antigravity:checkandcodex:checkgatemise run apply: they fail with a diff while runtime state drifts. Inspect the diff first. Integrate any intentional settings back into the repo config. Never keep workspace paths or runtime state.antigravity:resolveandcodex:resolveoverwrite the live file with the repo copy, dropping private runtime state and letting the app re-prompt.
- Follow Conventional Commits.
- Header MUST be <= 50 chars. Verify before committing.
- Template:
feat(scope): description.
- Use jj for all version control operations.
- Remote read operations are allowed.
- Do not push to the remote repo; leave this to the user.
AGENTS.md is the real file and CLAUDE.md is a symlink onto it, never a second
copy. The same holds for skills: .agents/skills is real, .claude/skills links
to it.
agents/is the source for~/.agents: the instruction file every agent loads and the skills that apply in every repo (herdr).[dotfiles]links it to~/.agentsand links itsAGENTS.mdto the names Claude, Codex and Antigravity look for.- The
~/.claude/skillslink exists because Claude Code reads only its own directories. Cursor reads the.agentspaths itself, so it needs no link. - A skill only about this repo goes in
.agents/skills/, with a.claude/skillssymlink beside it. There are none; add one only when a real procedure has proved itself, not to record what a source repo already explains. mise run update-skillspulls new upstream releases of the vendored global skills and rewritesagents/.skill-lock.json.- This repo is public. Keep employer names, private repo names and account
identifiers out of
agents/AGENTS.md; they belong in that employer's own repo.
See "Layout" in agents/AGENTS.md for the standard directory layout.