Tooling and Git Hooks
The repository uses mise.toml to install the project hook runner and
hk.pkl to define the hook behavior. criv.toml also declares project-level
tooling files, including Hawk’s hawk.toml, as source roots. The decision
record is ADR-0013.
Run the initial setup with:
mise install
The mise postinstall hook runs hk install --mise, so Git hook execution goes
through mise x and uses the pinned tool versions, including hk, Rust 1.97.1,
and actionlint. GitHub Actions security analysis is pinned here too: zizmor
checks workflow, composite action, and Dependabot definitions for risky CI/CD
patterns. Hawk uses the same Rust toolchain to check whether public Rust APIs
are required by the shipped criv binary. Release tooling is also pinned here:
Cocogitto calculates the next SemVer version from conventional commits, and
cargo-release updates Cargo workspace versions. hk 2 evaluates hk.pkl with its
bundled Pkl evaluator, so no separate pkl CLI is needed.
The top-level steps block in hk.pkl runs in the check, fix, and
pre-commit hooks. It holds the gatekeepers (merge-conflict markers, files
over 500 KB outside brag-output/, and private keys), the formatters, and the
workflow and shell linters. pre-commit adds gitleaks on the staged changes,
and check adds gitleaks on the Git history. hk fix does not stage its
changes; pass --stage to stage them.
Workflow YAML under .github/workflows/ is checked with actionlint in
pre-commit and the full check hook. zizmor runs in offline mode in the same
hooks so local validation does not require a GitHub token or network access.
This follow-up hook decision is ADR-0018.
Prose lint
Vale checks the prose in docs/. .vale.ini applies the built-in Vale
style, so no vale sync step is necessary. The pre-commit hook runs Vale on
changed files under docs/. The hosted vale CI job runs
vale-cli/vale-action on all of docs/. To run it locally:
mise exec -- vale docs
If Vale flags a correct project word, add it to
.vale/styles/config/vocabularies/criv/accept.txt. Add the (?i) prefix to a
common word. Do not add it to a proper noun or an acronym, so Vale enforces the
case.
ADR-0143 records this decision.
Local Rust cache
Mise 2026.8.16 or later routes this project’s cargo commands through
Mr. Boxington mbx 1.9.0. Run these commands after pulling the configuration:
mise install
mise reshim
mise exec -- cargo test --locked --workspace
An active mise shell also wraps plain cargo commands. Git hooks use the same
wrapper through hk install --mise. Do not run mbx setup --global for this
project. The checked-in configuration supplies the wrapper.
mbx shares compiler results between local worktrees. Its default cache policy
controls incremental compilation. Worktrunk still copies target/ with shared
filesystem blocks, and its removal hook still deletes each worktree’s copy.
The wrapper sets MBX_TARGET_VIEWS=0 to keep these independent directories.
Do not migrate a criv worktree to an mbx-managed target while this copy policy
is in use. Give simultaneous Cargo builds separate output directories, for
example --target-dir target/clippy for Clippy beside workspace tests.
The wrapper bypasses mbx when CI or GITHUB_ACTIONS is nonempty. Hosted builds
keep the compiler cache policy from
ADR-0108. Hawk also sets MBX_DISABLE=1,
clears RUSTC_WRAPPER, and keeps target/hawk for its build files.
Inspect the shared cache and preview its cleanup with:
mise exec -- mbx cache stats
mise exec -- mbx gc --dry-run
Deleting a worktree does not delete the shared compiler cache. mbx collects eligible cache entries under its own retention policy. Cache byte counts do not measure physical space saved by filesystem block sharing.
To bypass mbx for one command, including a local release or debugger build:
MBX_DISABLE=1 mise exec -- cargo build --release
To remove automatic wrapping, remove [wrappers.cargo] and
[wrappers.cargo.env] and the [env] entry for MBX_DISABLE from mise.toml.
Then run mise reshim. Existing
target/ directories remain valid Cargo output directories. The mbx tool pin
and shared cache can be removed separately.
ADR-0142 records this integration.
Issue #196 holds the proposed
performance comparison. A local trial on 8 September 2026 used three runs per
case on macOS/APFS, Rust 1.97.1, and mbx 1.9.0, before integration with PR #197.
The values below are medians.
Estimated disk allocation fell from 2.47 GiB to 2.04 GiB, including the mbx
cache. Empty-cache tests took 275 s with mbx versus 78 s with plain Cargo;
simultaneous tests and Clippy took 238 s versus 46 s. Warm and small-edit test
times showed no clear gain. Disk estimates used free-space changes after
deleting isolated trial outputs; they do not predict long-term cache growth.
Use MBX_DISABLE=1 when build speed is the priority for these workloads.
Running criv from a hook runner
criv does not install Git hooks and does not set core.hooksPath, per
ADR-0054. Setting that config replaces
the hook directory wholesale, which silently disables whichever runner the
repository already uses. Wire criv into your own runner instead.
The commands are stable:
| Stage | Commands |
|---|---|
| commit | criv watch --once, criv check --changed, criv enforce --stage commit |
| push | criv enforce --stage push |
watch --once refreshes .criv/state.json before the changed check. The
changed check is a staged, read-only partial fast path; plain criv check
remains the full manual and hosted authority per
ADR-0067. Run commit commands
in the listed order.
With hk, as this repository does in hk.pkl:
local criv_check_changed = new Step {
glob = List("**/*.md", "criv.toml")
check_first = true
check = "criv check --changed"
fix = "criv check --fix"
}
local criv_enforce_commit = new Step {
check = "criv enforce --stage commit"
}
hooks {
["pre-commit"] {
fix = true
steps {
["criv-check"] = criv_check_changed
["criv-enforce"] = criv_enforce_commit
}
}
}
With lefthook, in lefthook.yml:
pre-commit:
parallel: false
commands:
criv-watch:
run: criv watch --once
criv-check:
run: criv check --changed
criv-enforce:
run: criv enforce --stage commit
pre-push:
commands:
criv-enforce:
run: criv enforce --stage push
Keep parallel: false for the commit stage: check reads the state watch
writes, so they must not overlap.
Manual task entry points are:
mise run commit-msg -- .git/COMMIT_EDITMSG
mise run pre-commit
mise run pre-push
mise run fix
mise run perf
mise run vscode-build
mise run vscode-package
mise run release-plan
mise run release-auto
The hosted release workflow calls release-auto after CI passes. The command
uses Cocogitto to prepare and push the version commit. Hosted jobs build,
package, verify, attest, and publish that exact commit. Normal local development
needs only release-plan; release-auto remains the workflow helper and a
diagnostic entry point.
See releasing for the full sequence.
Pre-commit and pre-push are the automatic local validation boundary. Agents and contributors do not need to replay them with an aggregate command after each commit. The former local aggregate task has been removed.
Hosted CI invokes the complete hk check profile directly. Every step hk runs
is defined in hk.pkl with its command inline, per
ADR-0049. To inspect or debug one core
step explicitly:
hk check --all --plan # list every step
hk check --all --step hawk
Pre-push runs Clippy, workspace tests, Hawk, and criv push enforcement. Hawk is kept in both pre-push and the hosted CI profile so unnecessary public Rust APIs fail before hosted CI during normal local development.
mise run perf generates isolated vaults from the checked-in barrs-small
and criv-medium manifests, then measures an explicit release binary. Build
that binary first and identify its profile at the command line:
cargo build --release
mise run perf -- --binary target/release/criv --profile release
The script records repeated samples for cold, warm, and changed watch --once,
validation, CI enforcement, docs-only next-adr-id, orphan-docs, and
nodes --kind doc queries, the source-backed
nodes --kind code --without-docs query, and a no-op snapshot diff. Five
samples are collected by default; use --samples to change the count or repeat
--case to select cases:
mise run perf -- --binary target/release/criv --profile release --samples 9
mise run perf -- --binary target/release/criv --profile release --case check
Every recorded sample receives a fresh generated vault. Warm cases perform an untimed state-building run inside that vault before the recorded command, and the changed case mutates the manifest’s declared number of supported source files without changing their sizes. A unique result directory preserves the run identity, exact manifest copies, raw JSONL samples, stdout/stderr, and a JSON summary with min/median/max and median absolute deviation. Failed samples remain in the raw evidence and make the harness fail.
mise run perf-container runs one ignored smoke case through Testcontainers.
It builds criv and the harness inside a digest-pinned Rust Linux image. Docker
is only an optional execution environment: the same checked-in manifests and
generator define the vaults, results have their own container machine identity,
and this task is not part of hooks or hosted CI. A Docker-API-compatible runtime
is required. Large-workload measurement remains deferred.
Obsidian and VS Code validation are hosted-CI suites rather than automatic hk
steps. Contributors changing a companion should run its package scripts
directly. Run npm ci from the repository root first. The Obsidian companion
commands are:
npm audit --workspace criv-obsidian-plugin --audit-level=high
npm --prefix .obsidian/plugins/criv run format:check
npm --prefix .obsidian/plugins/criv run lint
npm --prefix .obsidian/plugins/criv test
npm --prefix .obsidian/plugins/criv run build:wasm
npm --prefix .obsidian/plugins/criv run build:plugin
npm --prefix .obsidian/plugins/criv run build
The combined build preserves the release and manual entry point by running the
Wasm build followed by the plugin build. The Wasm command invokes wasm-pack
through mise exec cargo:wasm-pack@0.15.0, so the repository’s pinned tools are
used rather than whichever Rust or wasm-pack appears first in the shell.
It writes criv_wasm.js, criv_wasm_bg.js, and criv_wasm_bg.wasm under
.obsidian/plugins/criv/pkg/. The plugin bundle keeps that runtime import
external, so a distributable plugin must carry the generated pkg/ directory
beside main.js.
If Obsidian reports that the criv Wasm runtime is unavailable, run the combined
build, confirm those three runtime files exist, and reload the plugin. A state
schema or parse error is separate: regenerate .criv/state.json with criv watch --once instead of rebuilding Wasm.
The VS Code-compatible extension lives in extensions/vscode-criv. Its local
tasks are exposed through mise and npm:
npm audit --workspace vscode-criv --audit-level=high
npm --prefix extensions/vscode-criv run build
npm --prefix extensions/vscode-criv run test
npm --prefix extensions/vscode-criv run test:integration
npm --prefix extensions/vscode-criv run lint
npm --prefix extensions/vscode-criv run format:check
npm --prefix extensions/vscode-criv run package
The integration command launches the VS Code extension test host and fails on JSON diagnostics for extension manifest and language configuration files. Its short temporary user-data and extension-installation paths avoid macOS IPC path limits. Hosted CI runs this command under Xvfb; it is not part of local hooks.
The extension uses a read-only LikeC4 preview as the default .c4 editor. It
selects the named view owned by the opened file. There is no fallback view: a
file that owns none states so and points at a file that declares a view.
Navigating inside the
diagram opens the file that owns the target view in the same editor group, so
the tab tracks the diagram. Use
Reopen Editor With → Text Editor to edit the DSL. The preview webview uses packaged extension
resources and does not use a CDN or a global LikeC4 command. Run criv watch --once after architecture or extension source changes. This keeps architecture
state current. The coding agent authors the repository Code model and views under
docs/architecture/code/, so watch does not replace them.
npm --prefix extensions/vscode-criv run package and mise run vscode-package build a local vscode-criv.vsix without publishing. mise run vscode-install builds that VSIX and installs it into the local editor; reload
the window afterwards. The
prepublish hook builds the Node.js Wasm target into
extensions/vscode-criv/pkg/; the VSIX includes criv_wasm.js and
criv_wasm_bg.wasm from that directory. If the extension reports an unavailable
Wasm runtime, rebuild build:wasm, recreate the VSIX, confirm those files are
present in the package, and reload the editor window. Regenerate invalid state
with criv watch --once; do not treat a state-validation error as a runtime
packaging failure.
The optional viewer is local-only under ADR-0087. The project does not publish it to the VS Code Marketplace or Open VSX. Release archives put the one viewer package next to the criv executable. Install it with an explicit editor selection:
criv install-editor --editor code
criv install-editor --editor cursor
Use --dry-run to validate the bundled viewer and local editor command without
changing editor state.
Use hk validate after editing hk.pkl. Prefer hk built-ins when one exists;
the commit message hook uses Builtins.check_conventional_commit instead of a
shell wrapper around hk util check-conventional-commit.
The hawk hk step runs Hawk with warnings denied against the shipped criv CLI.
The criv-wasm crate is excluded because its exported functions are consumed by
the separately built Wasm artifacts, outside the CLI binary’s Cargo graph. The
pre-push and full check hooks run Hawk whenever Rust sources, Cargo metadata,
or its configuration change. Hawk uses target/hawk for its instrumented Cargo
artifacts so its compiler work stays isolated from the other parallel checks.
This policy is captured in ADR-0043 and the
local/hosted validation boundary is
ADR-0061.