chore(tooling): migrate justfile and lefthook to mise tasks and hk - #23
Merged
Merged
Conversation
mise.toml now holds every command Focale runs; hk.pkl decides only when each task fires and against which files, and scripts/determinism-hashes.sh makes the determinism render reproducible locally. hk's pre-commit stashes unstaged work and runs against the staged content, so formatting no longer rewrites files you deliberately left unstaged — verified against the old lefthook config, which reformatted them in the working tree. mise also provisions convco, which lefthook's commit-msg hook assumed was already on PATH. convco goes through the cargo backend rather than a direct release download: it publishes no x86_64-apple-darwin binary, so binstall takes the prebuilt artifact where one exists and falls back to a source build instead of hard-failing. The pre-push steps are unglobbed on purpose. Each runs a workspace-wide cargo command that ignores hk's file selection, so a glob would only decide whether the step runs — and a push touching only Cargo.toml or rust-toolchain.toml would skip a gate CI still enforces.
Each job now runs the same `mise run <task>` the git hooks and developers run, so CI and the hooks cannot drift apart. The commits job drops cargo-binstall entirely — mise provisions convco — and no longer needs a Rust toolchain. A new Tooling job runs `mise run validate-hooks` so a broken hk.pkl fails in CI rather than at the next developer's commit; nothing machine-checked that file before. The determinism render block moves verbatim into scripts/determinism-hashes.sh behind `mise run determinism`, parameterised only by HASHES_OUT so the artifact names are unchanged. The compare job keeps its inline diff and says why: it has no checkout, so there is no mise.toml there to run a task from.
Both are superseded: every recipe is a mise task and every hook is an hk step. lefthook.yml was inert in fresh clones anyway — nothing ran `lefthook install`, so .git/hooks held only the stock samples.
Prerequisites, the commands table, the git-hooks section and the agent instructions now point at mise tasks, and state that mise.toml is the single source of truth the hooks and CI both call. Records that the determinism render/hash step is reproducible locally, and that `mise run hooks` installs into this clone only and needs mise on git's PATH.
Adding an hk.pkl validation job to CI without adding it to `mise run check` or pre-push made three claims false at once — mise.toml's "everything CI runs", AGENTS.md's CI-parity line, and README's "pushes should not fail CI". The task now runs in both, so a broken hk.pkl is caught before it reaches CI. Also: pin cargo-binstall like every other tool, since the README calls these pinned; stop claiming `hk validate` resolves task names (it does not — a typo'd `mise run` target validates clean); drop the README's `--global --mise` PATH remedy, which changes where the hook is registered rather than the PATH it needs; stop implying mise installs cmake; and run mise-action after the toolchain step so a binstall fallback to `cargo install` has a managed toolchain.
The previous commit's step reorder moved jdx/mise-action past the `mise run test` step in this job, so it failed with `mise: command not found`. Every job that invokes a task now sets mise up first.
Remove multi-line comments about cargo-binstall/convco and task execution that were not adding clarity to the configuration.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces
justfile+lefthook.ymlwith mise tasks + hk, and points every GitHub Actions job at the same tasks.Why
cargo clippy --workspace --all-targets -- -D warningswas written in three places —justfile,lefthook.ymlandci.yml— and could drift in three directions. Two further problems surfaced while doing this:lefthook.ymlwas inert..git/hooks/in a fresh clone holds only the stock.samplefiles; nothing ranlefthook install. Installing it by hand didn't help either: its commit-msg hook died withsh: convco: command not found, because the README only linked convco and left installing it to you.cargo fmtover the whole working tree. I reproduced this: with file A staged and an unrelated edit left unstaged in file B, B was rewritten in the working tree (sha changed, reformatted). It wasn't swept into the commit, but the bytes you deliberately left out were still not yours any more.What changed
mise.tomlis the single source of truth for every command.hk.pklcontains nocargoorconvcoinvocation at all — it decides only when each task runs. CI calls the same tasks.mise.tomlhk,cargo-binstall,convco) + every taskhk.pklmise run <task>scripts/determinism-hashes.shdeterminism.ymljustfile,lefthook.yml.github/workflows/ci.ymlmise run <task>;cargo-binstallstep dropped; newToolingjob.github/workflows/determinism.ymlmise run test+mise run determinism.gitignoremise.local.toml,.mise.local.toml,/hashes*.txtREADME.mdAGENTS.mdjust check→mise run check; records the single-source-of-truth ruledocs/verification.mdNo application code is touched.
HARD-DET,HARD-VER,HARD-RUST,HARD-FSare unaffected;HARD-LOCALcovers the app, not dev tooling; mise/hk/convco are MIT developer tools, not linked into any crate (HARD-LICENSE).Every command string is character-identical to its predecessor —
cargo fmt,cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace,cargo build --workspace,cargo run -p focale-app --,convco check <range>,convco check --from-stdin.The four gates, in all three places
mise run checkmise run fmt-checkmise run lintmise run testmise run validate-hooksmise run commitsmise run determinismBehaviour changes
stash = "git"saves unstaged changes, runs against the staged content, stages only the files it selected, restores the rest.mise run determinismruns the determinism check locally. It was CI-only inline bash before.Conventional commitsjob no longer installs a Rust toolchain, and dropscargo-bins/cargo-binstall@main— a floating branch ref in the supply chain.ToolingCI job runshk validate, so a brokenhk.pklfails in CI rather than at the next contributor's commit. This adds one required status check.mise.tomlfor anyone without it.Decisions taken
Builtins.cargo_fmt/cargo_clippyare deliberately unused: they exist to scope a linter to{{files}}, butcargo fmt/clippyare workspace-wide regardless, so the builtins would only have re-stated the flags in a second file.check_conventional_commitbuiltin validates a single message and hk has no range checker — pre-push and CI need one, so using the builtin would have meant two implementations of the same rule. Rationale recorded inhk.pkl.rust-toolchain.toml+dtolnay/rust-toolchain@stable. mise[tools]manages only hk/convco/binstall, so there is no second Rust version declaration.cargo:backend. It publishes nox86_64-apple-darwinbinary;cargo-binstalltakes the prebuilt artifact where one exists (all CI runners, Apple Silicon, Windows) and falls back to a source build on Intel macOS instead of hard-failingmise install.Cargo.tomlorrust-toolchain.tomlwould skip a gate CI still enforces.mise run checkis sequential, like thejustfilerecipe.dependsruns in parallel, which buys nothing against cargo's target lock and interleaves three cargo logs.Residual duplication (could not be collapsed)
mise.toml[tools], andhk.pkl'samendsURL +min_hk_version. Inherent to hk's Pkl packaging. TheToolingjob catches a bad bump.determinism.yml'scomparejob keeps an inlinediff. That job has noactions/checkout, so there is nomise.tomlthere to run a task from. Recorded in a comment at the step.Validation
Local, on macOS/aarch64 at
08afdda:mise run checkmise run validate-hookshk.pkl is validactionlint .github/workflows/*.ymlmise run determinismgit pushCI at
08afdda: all 8 checks pass — Format, Clippy, Test, Tooling config, Conventional commits, Render (x86_64), Render (aarch64), Compare architectures.The
Compare architecturesjob emitted hashes byte-identical to the local macOS/aarch64 run, across all 9 labels — so the script lift is behaviour-preserving on macOS/aarch64, Linux/x86_64 and Linux/aarch64.Hook behaviour, exercised end-to-end in throwaway clones:
lefthook.yml: the unstaged file was rewritten.this is not a conventional commitrejected (exit 1, HEAD unmoved);chore: good message testaccepted; a realgit mergewithMerge branch 'side-branch'passed via theMERGE_HEADexemption.commitsfails, push blocked. Branch with a clippy error:lintfails, push blocked..rsfiles.hk run pre-push --planselects every step; with the globs it reported○ fmt-check (no files matched filters).Coverage gaps
mise run determinismproves the script runs, not that architectures agree. TheHARD-DETguarantee still lives entirely in the two-arch CI matrix.scripts/determinism-hashes.shhas no lint gate — nothing runs shellcheck/shfmt over it (pre-existing forscripts/fetch-models.sh, but this script now carries aHARD-DET-critical step).hk validateparseshk.pklbut does not resolve task names or reject unknown fields. A typo likemise run lntvalidates clean and would surface at hook time.[tools]set. Accepted for consistency; it does mean a release-fetch hiccup can fail an unrelated job.mise.toml, but there is nomise.lockrecording resolved artifacts.Open questions
scripts/get a shellcheck gate, now that aHARD-DETstep lives there? Out of scope here.mise.tomlset[settings] idiomatic_version_file_enable_tools = []as defence-in-depth, so a contributor who globally enabled rust idiomatic version files doesn't get mise shadowing rustup? Today's default is node-only, so this is not a live bug..versionrcis astandard-version(Node) config that nothing in the repo consumes — release-plz uses its own changelog config and there is no Node toolchain. Left untouched deliberately; delete separately?