--- name: mise description: > mise (dev tools version manager) expert for full-stack monorepos. Use this skill whenever the user asks to set up mise, add tools to mise, create or edit a mise.toml, define mise tasks, manage Java/Node/pnpm/Maven versions, or replace npm scripts / Makefile with mise tasks. Also trigger for phrases like "pin the node version", "add a task for", "run with mise", "how do I manage tool versions", "set up dev environment", or any question about .mise.toml configuration. Always use this skill proactively when scaffolding new projects or services — every WellForge project should have a mise.toml. --- # mise — Dev Tools Version Manager Authoritative reference for configuring mise in Spring Boot Kotlin + React TypeScript monorepos. See `references/` for deep dives on specific areas. --- ## Pinned tool versions for WellForge projects These versions are verified to work together. Use them verbatim unless the user specifies otherwise. ```toml [tools] java = "temurin-21" node = "22" maven = "3.9.9" # via mise-plugins/mise-maven pnpm = "10" ``` **Why temurin, not openjdk?** `temurin` (Eclipse Adoptium) sets `JAVA_HOME` correctly and integrates with IntelliJ auto-detection. `openjdk` from mise does not set `JAVA_HOME` by default on macOS. **Maven plugin** — must be installed first: ```bash mise plugin install maven https://github.com/mise-plugins/mise-maven ``` --- ## Monorepo layout — one mise.toml per service, root for shared tools ``` project/ ├── mise.toml ← shared tools (java, node, maven, pnpm) + root tasks ├── backend/ │ └── mise.toml ← backend-specific tasks (compile, test, run) └── frontend/ └── mise.toml ← frontend-specific tasks (dev, build, lint, e2e) ``` ### A root task cannot depend on a subdirectory task by name mise merges config walking **upward** from the cwd, so the root config never sees `backend/mise.toml`'s tasks. A root aggregate written the obvious way — ```toml [tasks.install] depends = ["backend:install", "frontend:install"] # ✗ task not found: backend:install ``` — fails at run time. Verified on mise 2026.9.0, and it shipped in both WellForge presets: `mise run install/build/test/lint/dev` was broken in **every generated project** until 2026-09-20, which is exactly what "full `mise run install` on a generated project" being an outstanding pilot item had been hiding. `//backend:install` addressing and an `experimental_monorepo_root` setting do not exist in that version either — the latter warns `unknown field` and is otherwise ignored, so it looks like it worked. The portable pattern, and what the presets now ship: declare the per-service task **at the root** with `dir`, and let it re-enter mise there. ```toml [tasks.install] # the aggregate depends = ["backend:install", "frontend:install"] [tasks."backend:install"] # the pointer dir = "backend" run = "mise run install" # cwd is backend/ → backend/mise.toml's `install` wins ``` Each task keeps exactly ONE definition, in its service's file; the root entries are pointers. This relies on a subdirectory task shadowing a same-named root task when cwd is inside that subdirectory — confirm with `mise task info install` from `backend/` (the `Source:` line must be the service's own file), and re-confirm after a mise upgrade. mise walks up the directory tree and merges configurations hierarchically — the root `mise.toml` sets the tool versions used everywhere; service-level files add tasks without re-declaring tools. --- ## Root mise.toml template ```toml # mise.toml — project root # Requires: mise plugin install maven https://github.com/mise-plugins/mise-maven [tools] java = "temurin-21" node = "22" maven = "3.9.9" pnpm = "10" [env] # JAVA_HOME is set automatically by mise when using temurin # Add project-level env vars here (non-sensitive only — secrets go in .mise.local.toml) # _.file = ".env" ← uncomment to load .env automatically [settings] experimental = true # required for monorepo task features [tasks.install] description = "Install all dependencies (backend + frontend)" depends = ["backend:install", "frontend:install"] [tasks.build] description = "Build all services" depends = ["backend:build", "frontend:build"] [tasks.test] description = "Test all services" depends = ["backend:test", "frontend:test"] [tasks.lint] description = "Lint all services" depends = ["backend:lint", "frontend:lint"] [tasks.dev] description = "Start all services in development mode" depends = ["backend:dev", "frontend:dev"] ``` --- ## backend/mise.toml template ```toml # backend/mise.toml # Tools are inherited from root — do not redeclare them here [tasks.install] description = "Resolve Maven dependencies" run = "./mvnw dependency:resolve -q" sources = ["pom.xml"] [tasks.build] description = "Compile the backend" depends = ["install"] run = "./mvnw clean compile -q --no-transfer-progress" sources = ["pom.xml", "src/**/*.kt"] outputs = ["target/classes/**/*"] [tasks.test] description = "Run backend tests" depends = ["build"] run = "./mvnw test -q --no-transfer-progress" sources = ["src/**/*.kt"] [tasks.run] description = "Start the Spring Boot service (Docker Compose starts automatically)" run = "./mvnw spring-boot:run" [tasks.lint] description = "Run ktlint check" run = "mvn com.github.gantsign.maven:ktlint-maven-plugin:check -q" sources = ["src/**/*.kt"] [tasks."lint:fix"] description = "Run ktlint format" run = "mvn com.github.gantsign.maven:ktlint-maven-plugin:format -q" sources = ["src/**/*.kt"] [tasks.generate] description = "Generate jOOQ sources from Liquibase" run = "./mvnw generate-sources -q --no-transfer-progress" sources = ["src/main/resources/db/changelog/**/*.sql"] outputs = ["target/generated-sources/jooq/**/*"] ``` --- ## frontend/mise.toml template ```toml # frontend/mise.toml # Tools are inherited from root — do not redeclare them here [tasks.install] description = "Install frontend dependencies" run = "pnpm install --frozen-lockfile" sources = ["package.json", "pnpm-lock.yaml"] outputs = ["node_modules/.modules.yaml"] [tasks.dev] description = "Start Vite dev server" depends = ["install"] run = "pnpm dev" [tasks.build] description = "Production build" depends = ["install", "lint"] run = "pnpm build" sources = ["src/**/*.{ts,tsx}", "index.html", "vite.config.ts"] outputs = ["dist/**/*"] [tasks.lint] description = "ESLint check" run = "pnpm exec eslint src --max-warnings 0" sources = ["src/**/*.{ts,tsx}"] [tasks."lint:fix"] description = "ESLint fix" run = "pnpm exec eslint src --fix" [tasks.format] description = "Prettier format" run = "pnpm exec prettier --write src" [tasks."format:check"] description = "Prettier check" run = "pnpm exec prettier --check src" [tasks.test] description = "Run Vitest unit tests" depends = ["install"] run = "pnpm test --run" [tasks.e2e] description = "Run Playwright E2E tests" depends = ["build"] run = "pnpm e2e" ``` --- ## Key CLI commands ```bash # First-time setup mise plugin install maven https://github.com/mise-plugins/mise-maven mise install # install all pinned tools mise trust # trust .mise.toml (required once per project) # Running tasks mise run dev # start everything mise run backend:build # single service mise run test # all tests in parallel mise run build --jobs 2 # limit parallelism # Version management mise use java@temurin-21 # pin in current .mise.toml mise use --global node@22 # pin globally mise ls # show active versions mise ls --current # show only currently active # Environment mise env # show all env vars mise would set mise exec -- mvn --version # run command with mise environment (scripts/CI) ``` --- ## .gitignore additions ```gitignore # mise local overrides — never commit .mise.local.toml .mise.*.local.toml ``` --- ## .mise.local.toml — machine-local secrets and overrides ```toml # .mise.local.toml — gitignored, never committed [env] OPENAI_API_KEY = "sk-..." DATABASE_URL = "postgresql://localhost/mydb_local" ``` Use `_.file = ".env"` in the main `mise.toml` only for non-sensitive defaults. Secrets always go in **`.mise.local.toml`** — one place, not a choice. A gitignored `.env.local` is *not* an equivalent option here: the plugin's `pre-bash-guard.sh` blocks commands mentioning `.env` files, so an agent cannot create, read or copy one, and half a convention is worse than none. See the `connections` skill → `references/environments.md` (the authority). --- ## Updating the session-start hook The `session-start.sh` hook in the wellforge plugin injects context at session start. When mise is set up in a project, it should also verify tool versions: ```bash # Add to session-start.sh after the monorepo services block: echo "## mise tool versions" if command -v mise &>/dev/null && [ -f "$PROJECT_DIR/mise.toml" ]; then mise ls --current 2>/dev/null | awk '{print "- " $1 " " $2}' else echo "(mise not configured)" fi ``` --- ## Common mistakes to avoid | Mistake | Correct approach | |---|---| | Redeclaring `[tools]` in service-level mise.toml | Only declare tools at root — services inherit | | Using `java = "openjdk-21"` | Use `java = "temurin-21"` for proper `JAVA_HOME` | | Hardcoding secrets in mise.toml | Use `.mise.local.toml` (gitignored) | | Running `mvnw` without `mise exec` in CI | Use `mise exec -- ./mvnw ...` in CI pipelines | | Defining tasks with duplicate tool version pins | Tool version pinning belongs in `[tools]`, not per-task | | Forgetting `mise trust` after cloning | Always run `mise trust` on first clone |