# Checks - **Generated:** 2026-10-27 - **Checks:** 77 - **Tools:** 47 - **Shell-script checks:** 10 - **Checks with a repair pass:** 21 - **Rules:** 358 custom local rules, or non-default strict linter settings - **Meta rules:** 19 of those rules are rules about rules Every check is listed across every `MISE_ENV`, filed under the tool it runs. Each entry names the env whose config declares it, the aggregate that runs it as part of `mise run check`, the `fix` or `fmt` pass that repairs what it reports, where one does, and the config files it reads. `mise run fix` runs every such pass. Generated by `mise run gen:checks` from the task tables in `.mise/config*.toml`, the rule files under `config/` and the `[workspace.lints]` in `Cargo.toml`. It is not held to them on every change: `mise run checks-md-check` fails only once the generation date above is more than 7 days old. This repository even has rules about rules. A meta rule, marked `(meta)` below, checks the checks rather than the code: that a rule held to every file carries no exemption, in its config or in a suppression comment; that every policy is tested; that a rule's path filters say what they mean; that the duplication baseline only shrinks. Without them a check could be weakened quietly and still pass. A YAML rule is one when its `metadata` sets `meta: true`, and a Rego policy, with every rule in it, when its `METADATA` annotation does. ## `action-validator` - `action-validator-check` (`default` env; run by `check:rust`) -- Validate GitHub Actions workflow + composite-action YAML. ## `actionlint` - `actionlint-check` (`default` env; run by `check:rust`) -- Lint GitHub Actions workflows (actionlint): local-action input validation, expression syntax, shellcheck. ## `agent-lint` - `agent-lint-check` (`default` env; run by `check:rust`) -- Lint this repository's agent configuration (AGENTS.md, .claude/) with agent-lint. ## `ast-grep` - `ast-grep-check` (`default` env; run by `check:rust`) -- Run ast-grep structural-search rules. Repaired by `ast-grep-fix`. Config: `config/ast-grep/sgconfig.yaml`. - `ast-grep-check-rule-tests` (`default` env; run by `check:rust`) -- Run the ast-grep rule tests against their valid and invalid cases (ast-grep test). Config: `config/ast-grep/sgconfig.yaml`. Rules, from `config/ast-grep/rules`: - `command-requires-command-error` -- A file that imports `std::process::Command` via `use` must also `use command_error::CommandExt` (import it as `use command_error::CommandExt as _;`) so command execution goes through the checked methods (`status_checked` / `output_checked` / `spawn_checked`), whose errors carry the command line and exit status -- a raw `std` `Command`'s `.status()` / `.output()` / `.spawn()` errors carry none of that. If a call genuinely needs the raw outcome (e.g. a test that asserts a non-zero exit, or an exit-code proxy), fully-qualify `std::process::Command` at the call site instead of importing it, so this rule does not apply. - `const-at-top` -- Declare module-level `const` and `static` items at the top of the file, after the `use` lines and before any type, trait, `impl` or function. A reader then meets the module's fixed values before the code that uses them. - `derive-attribute-order` -- Put `#[derive(...)]` before every inert or helper attribute on the same item -- `#[non_exhaustive]`, `#[repr]`, `#[cfg_attr]`, and derive helpers such as `#[serde]`, `#[command]`, `#[strum]` or `#[asyncapi]`. Only doc comments, `#[cfg]`, `#[expect]` and attribute macros that must see the item before the derive runs (`#[serde_inline_default]`, `#[serde_as]`, `#[wasm_bindgen]`) may come first. One order everywhere puts an item's derive list in the same place on every type, and keeps each helper next to the derive that reads it. A new attribute macro that has to precede the derive joins the allow-list in this rule. - `doc-summary-ends-with-period` -- A doc comment's first line must be a complete one-line summary ending in terminal punctuation (`.`, `!`, or `?`). Put any elaboration in a following paragraph after a blank `///` line so the summary stays a single line -- rustdoc and readers treat that first line as the item's summary. - `gha-check-lane-fetch-depth-0` -- The check lane's `actions/checkout@...` step must declare `fetch-depth: 0` in its `with:` block. Two of the checks this lane runs read the commit graph rather than the worktree. At depth 1 the only commit that exists is the tip, so every commit hash written into a tracked file resolves to nothing and the lane fails on a repo that is perfectly fine -- while the branch-freshness check, having no `origin/main` to compare against, quietly skips itself and reports success. The history is a few MiB, so fetching all of it costs close to nothing here. - `gha-checkout-fetch-depth-1` -- Every `actions/checkout@...` step in these workflows must declare `fetch-depth: 1` in its `with:` block. Explicit-beats-implicit (the action default is already 1, but pinning it at the call site keeps the value visible in review and ensures a future default change doesn't silently start cloning more), and the rule's primary purpose is to prevent `fetch-depth: 0` from creeping back in -- nothing these lanes run reads the commit graph, so cloning every commit just burns runner minutes and disk. The check lane is excluded here, and held to the opposite requirement, because two of the checks it runs do read the commit graph and resolve nothing at depth 1. - `gha-default-shell-bash` -- Every GitHub Actions workflow must declare the default run shell as bash with -euo pipefail, exactly: defaults: run: shell: bash --noprofile --norc -euo pipefail {0} so steps run in bash on every runner (Windows included) with strict-mode flags on every step -- no per-step `set -euo pipefail` boilerplate needed. GHA's bare `shell: bash` already implies `-e -o pipefail`; this explicit form additionally adds `-u` (unset variable -> error). (Pairs with gha-no-step-shell, which forbids per-step `shell:`.) - `gha-job-timeout-minutes` -- Every GitHub Actions job must declare `timeout-minutes:` at the job level. The runner's default per-job timeout is 360 minutes (6 hours), which is long enough that a hung job (deadlocked test, infinite-recurse cargo resolver, stuck `docker pull`) drains the runner-minutes budget before anyone notices. Pin every job to a realistic ceiling for that job's work. Use `${{ matrix.timeout }}` or similar if the right value varies by entry. - `gha-no-folded-strip` -- Don't use `>-` (folded block scalar with strip-chomping) in workflow YAML. The output reflows whitespace and silently swallows trailing newlines, which bites when the step's `run:` is a shell command that depends on line breaks -- a `>-` block joins lines with spaces, not newlines. Use either: - a plain single-line `run: `, or - a literal block `run: |` (which preserves newlines verbatim). - `gha-no-step-shell` -- Don't set `shell:` on a workflow step. Every workflow defaults to bash (defaults.run.shell) and steps must use it -- write the step in bash (e.g. `sc`/`net`/`printf` instead of PowerShell cmdlets, even on Windows runners, which have Git Bash) rather than overriding the shell per step. No per-step shell override is permitted in these workflows. - `gha-no-step-shell-upstream-cache` -- In upstream-cache.yaml the only permitted per-step `shell:` override is `shell: msys2 {0}` -- the wrapper msys2/setup-msys2 requires for the augeas Windows build to run inside its MINGW64 env (Git Bash isn't a substitute). Any other shell override must use the workflow default bash. - `gha-standard-concurrency` -- Every GitHub Actions workflow must declare the standard top-level concurrency block exactly: concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} This groups runs per workflow per ref, and cancels older in-progress runs on a PR ref only -- so a push to main never cancels its predecessor (lest the merge-queue lose a result), but PR pushes don't burn runner minutes on superseded commits. Deviating from this group/key shape leads to surprising cancellation behaviour. - `gha-strategy-fail-fast-false` -- Any job that declares `strategy:` (matrix or otherwise) must also set `fail-fast: false` inside it. GHA's default of `fail-fast: true` cancels every other matrix entry the moment one fails, which hides the failure modes specific to other OS / version / runner cells -- exactly the matrix cells we added the matrix to exercise. Opt out explicitly: strategy: fail-fast: false matrix: ... - `inherent-impl-first` -- Put a type's inherent `impl` block before its trait `impl`s in the same module. A reader then meets the type's own constructors and methods first, and the trait impls that follow read as what they are: the type's conformance to interfaces defined elsewhere. - `new-fn-returns-option` -- This function returns `Option`, and a new one may not. The return type is where the reason a call failed would have gone: `Result` carries it, `Option` discards it at the moment it was known. `None` says "there isn't one" and nothing else -- not which input was wrong, not which file was missing, not which subprocess failed -- so the caller invents an explanation, and a missing tool surfaces three layers up as an empty list rather than as the sentence naming the tool. Return `Result` with an error that says what happened. `Option` is for a value that is legitimately absent without anything having gone wrong, which is rarer than it looks. The list below is every function that returned `Option` when this rule was written. It is a record of what is already here, not a pattern to copy: a name joins it only with the operator's sign-off, and the point of naming them one by one is that adding to it is a decision somebody makes rather than a default anything falls into. Prefer emptying it -- a function that leaves the list has had its failures given names. - `no-allow-attributes` -- `#[allow()]` and `#![allow()]` are forbidden. Use `#[expect(, reason = "...")]` (or `#![expect(...)]` at module / crate scope). `#[expect]` requires the lint to actually fire, so the suppression is removed automatically when the underlying issue is fixed upstream -- `#[allow]` can sit silently forever. This rule complements the workspace's `clippy::allow_attributes` lint, which doesn't fire on inner attributes (`#![allow(...)]`) -- see . - `no-assert-eq-bool-literal` -- `assert_eq!`/`assert_ne!` with a `true` or `false` literal as either operand is an `assert!` written the long way. Assert on the boolean itself -- `assert!(x)`, `assert!(!x)` -- and when the value is not a `bool` (a parsed `serde_yaml::Value` field, say), convert it first: `assert_eq!(value.as_bool(), Some(true))` keeps a failure message that shows what the value actually was. This mirrors `DeepSource RS-W1024`. clippy's `bool_assert_comparison` covers only operands whose type is `bool`, so a literal compared against any other type gets past it. - `no-cargo-crates` -- The `cargo_*` crates (`cargo_metadata`, `cargo_toml`, ...) are forbidden. They drive or emulate cargo itself -- most run `cargo metadata`, which takes the workspace lock -- when what Rust code here needs is a manifest, which the `toml` crate reads directly. - `no-cargo-manifest-dir` -- `CARGO_MANIFEST_DIR` is forbidden outside the project-root helper. It points at the crate directory, not the repository root, so reaching repo files through it needs brittle `..` hops. Locate the repository root with `et_path::find_project_root_from_manifest()` in a `build.rs` (compile time) or `edge_toolkit::config::get_project_root()` (runtime) instead. - `no-concat-raw-strings` -- `concat!(...)` of raw-string (`r#"..."#`) fragments is forbidden -- it is an unreadable way to build one string (a regex, an embedded template) out of a stack of `r#"..."#,` lines. Use a single multi-line raw string literal instead, or a named `const`, or -- for a regex -- `(?x)` verbose mode so the pattern can span lines without concatenation. Plain-string `concat!` (wrapping a long message, `concat!(env!(...), "...")`) is fine; only raw-string fragments are banned. - `no-consecutive-expect` -- Consecutive `#[expect(...)]` attributes on the same item. Merge them into a single `#[expect(lint_a, lint_b, reason = "...")]` (one attribute can list several lints). Stacking separate `#[expect]`s -- even with a comment between them -- fragments the suppression and makes it easy to leave one behind when the underlying issue is fixed. Applies to inner `#![expect(...)]` too. - `no-current-dir` -- `std::env::current_dir()` is forbidden outside the project-root helper. The current directory is not a reliable anchor for repository files -- it depends on where the binary happens to be invoked. Use `edge_toolkit::config::get_project_root()` at runtime, or `et_path::find_project_root()` from a build script at compile time; both locate the repository root via the `.dprint.jsonc` marker. - `no-doctest` -- Doctests are forbidden. Move runnable examples into a `tests/` file (every test file must start with `#![cfg(test)]`). - `no-env-macro` -- `env!` / `option_env!` read a compile-time environment variable and are forbidden for app config -- read configuration through a `serde`-derived struct deserialised with `serde-env` (see the `no-std-env-var` rule and `et-ws-runner-common::config`) so every variable is declared in one typed place. Only these cargo-provided constants, which have no serde-env equivalent, are exempt: - `CARGO_BIN_EXE_` -- the built binary's path, injected by cargo for an integration test. - `CARGO_PKG_VERSION` / `CARGO_PKG_NAME` -- crate identity baked into the binary. - `ET_WIT_DIR` -- the WIT directory injected at build time for wit-bindgen. `CARGO_MANIFEST_DIR` is deliberately NOT exempt -- use `edge_toolkit::config::get_project_root()` at runtime (see the `no-cargo-manifest-dir` rule). - `no-env-macro-in-attribute` -- `env!` / `option_env!` inside an attribute are banned exactly as they are anywhere else, with the same allowlist of cargo-provided constants (`CARGO_BIN_EXE_`, `CARGO_PKG_VERSION`, `CARGO_PKG_NAME`, `ET_WIT_DIR`). Read configuration through a `serde-env` struct instead, and locate repo files from the project root rather than from `CARGO_MANIFEST_DIR`. - `no-eq-bool-literal` -- `==`/`!=` with a `true` or `false` literal as either operand is a boolean written the long way. Use the boolean itself -- `x`, `!x` -- and when the value is not a `bool` (a parsed `serde_yaml::Value` field, say), convert it and keep the operator: `value == true` becomes `value.as_bool() == Some(true)`, and `value != false` becomes `value.as_bool() != Some(false)`. Reach for `value.as_bool().unwrap_or_default()` only where a value that is not a boolean is meant to read as `false`. This mirrors `DeepSource RS-W1001`. clippy's `bool_comparison` covers only operands whose type is `bool`, so a literal compared against any other type gets past it. - `no-expect-used-suppression` -- Suppressing `clippy::expect_used` is forbidden -- `.expect("...")` messages are not wanted in this codebase, so the workspace-denied lint must stay in force everywhere. Use `.unwrap()` instead: tests get it for free (clippy.toml's `allow-unwrap-in-tests`), and non-test code that genuinely must panic suppresses `clippy::unwrap_used` narrowly with a reason, or better, handles the error. No autofix: these attributes usually list other lints that must be kept, so removing just the `clippy::expect_used` entry (and rewriting the `.expect(...)` calls it permitted) is context-dependent. - `no-extern-crate-self` -- `extern crate self as ;` re-introduces the crate's own name as a path root inside its library, letting code write `::foo` instead of `crate::foo` / `self::foo`. Without it, rustc already rejects own-name paths in lib code (E0433); the alias is only needed by proc-macro crates whose generated code emits `::::...`. This workspace has none, so use `crate::` / `self::` for local paths instead. - `no-inline-mod` -- Inline `mod X { ... }` blocks are forbidden. Move the module body into a separate file (`X.rs` or `X/mod.rs`) and declare it with `mod X;` instead. - `no-log-crate` -- Log through `tracing`, not the `log` facade: `use tracing::{info, warn};` then `info!(...)`. A `log` record reaches the OTLP pipeline only through the binaries' bridge, flattened to a string with no structured fields and no span, and mixing the two leaves a crate's logging split between two APIs. - `no-map-err` -- `.map_err(...)` is forbidden. Convert through `From` (thiserror's `#[from]` / `#[error(transparent)]`) and rely on `?`, or define an extension trait whose impl carries the only `map_err` for the pattern. At actix-web boundaries, derive `ResponseError` via `actix-web-thiserror` instead of building `actix_web::error::Error*` values inside `map_err` closures. - `no-metadata-literal` -- The string literal `"metadata"` is forbidden: it is the argument that runs `cargo metadata`, which takes the workspace lock and so stalls, or is stalled by, every other cargo command running at the same time. Read the workspace from its manifests instead -- the root `Cargo.toml`'s `[workspace].members`, then each member's own `Cargo.toml` -- as et-int-gen's HELP.md discovery does. A `"metadata"` meaning anything else (a Kubernetes object's `metadata` key, say) takes a one-line `ast-grep-ignore` naming why. - `no-mixed-doc-line-comments` -- Doc comment (`///`) is adjacent to a regular `//` comment. Either turn the regular comment into a doc comment (`///`) or move it away from the doc block -- readers expect a contiguous `///` run to be the item's rustdoc. - `no-mod-in-tests` -- `mod` is forbidden inside test files (`tests/**/*.rs` and `src/test_*.rs`). Keep shared test helpers in a test-support library crate -- `et-test-helpers` for low-dependency helpers, or a dedicated crate such as `et-ws-test-server` / `et-test-otlp` -- and `use` them, rather than a `tests/common/mod.rs` include or an inline `mod { ... }`. A library gives each helper one compiled home (so coverage and lints see it once) instead of recompiling it per test binary, and it keeps integration-test files flat: one file per test binary. - `no-non-ascii` -- Non-ASCII character in a string literal or doc comment. Stick to ASCII in source: use `--` for an em-dash, `->` for an arrow, and straight quotes for curly quotes, etc. ASCII-only source keeps terminals, log scrapers, and `grep` reading the same bytes the editor shows. - `no-relative-path-literal` -- Relative path literals ("." / ".." / "./..." / "../...") are forbidden: they hardcode a location relative to the process's current directory or this crate's directory, which is brittle. Anchor paths to the repository root instead -- `edge_toolkit::config::get_project_root()` (runtime) or `et_path::find_project_root_from_manifest()` (build scripts) -- and join the remainder onto it. (Char literals like `'.'` for splitting are unaffected.) - `no-result-alias` -- Defining a `type Result<...>` alias shadows `std::result::Result` at file scope and makes every `Result` ambiguous to readers. Spell the error type at each call site instead (`Result`). - `no-result-string-err` -- `Result<_, String>` flattens any typed error to a free-form string at the point of failure -- losing the source variant, the ability to branch on it, and the chain of causes. Define a `thiserror` enum (or reuse one of the workspace's `*Error` types) and return that instead. - `no-self-call-in-default` -- A `Default::default` body may not delegate to another constructor of `Self` (`Self::new()`, `Self::from_parts(..)`) (`DeepSource RS-A1008`). Whenever that constructor is, or later becomes, written in terms of `Self::default()` -- the usual shape for a builder or a `new` that fills in the rest -- the two call each other until the stack overflows, and nothing at compile time says so. Build the value directly in `default()`, and let the other constructors call it rather than the reverse. - `no-shadow-result` -- Don't shadow `std::result::Result` at file scope -- every bare `Result` then becomes ambiguous to readers. Either spell the error type at each call site (`Result`), reference the local alias by its qualified path (`crate::Result`, `et_int_gen::Result`), or alias the import (`use foo::Result as FooResult;`). - `no-spawn-cargo` -- Rust code must not run cargo or any cargo subcommand binary (`cargo-`) as a subprocess. A nested cargo competes for the same build lock and target directory as whatever invoked it, so it stalls behind or rebuilds over the outer build, and it ties the code to a toolchain being on `PATH`. Read what you need from the manifests, link the crate whose data you want, or run cargo from a mise task instead. A test that genuinely has to build another workspace binary takes a one-line `ast-grep-ignore` naming why. - `no-std-env-var` -- `std::env::var` / `std::env::var_os` is forbidden for app config. Read configuration through a `serde`-derived struct deserialised with `serde-env` (see the runner `config` modules and `et-ws-runner-common::config`), so every variable is declared in one typed place with defaults and validation. Exemptions, intentionally narrow: - `RUST_*` (e.g. `RUST_LOG`, consumed by tracing) -- diagnostics. - `ET_TEST_*` -- test-only switches; production binaries must NOT branch on these. The integration test in services/ws-wasi-runner/tests/ sets one to opt the spawned runner into a macOS-only fast-exit path. - `MISE_*` (e.g. `MISE_ENV`) -- the mise tool's own state, not app config; reading these lets helpers like `mise_env_includes` reflect tooling behaviour without round-tripping through a struct. Every exemption above is a name prefix, which keeps it self-limiting: a variable has to be named into the exemption to use it. A bare variable name would not be -- it would open that read to every file in the repo, including production code that has a config struct to put it in. `PATH` was such an entry, and is now a one-line `ast-grep-ignore` on the single test helper that shapes `PATH` for a stand-in. - `no-std-fs` -- `std::fs` is forbidden -- its `io::Error`s carry no path, so a failure reads "No such file or directory" with no clue which file. Use `fs_err` (`fs-err` workspace dep), whose drop-in wrappers attach the offending path to every error. Import it as `use fs_err as fs;` and call `fs::read`, `fs::write`, `fs::File::open`, etc., exactly as you would `std::fs`. - `no-string-literal-line-continuation` -- Backslash-newline inside a Rust string literal is a line continuation (the `\` + newline + leading whitespace all get elided at parse time). Banned per the repo-wide no-trailing-backslash rule. Use `concat!("a", "b")` to assemble a multi-line string literal, keep it on one line, or drop to a raw string `r"..."` if the content really needs literal backslashes at line ends. A generic-regex rule catches the same pattern repo-wide; this variant is the Rust-AST-aware companion, and runs faster. - `no-string-new` -- `String::new()` is banned (`DeepSource RS-W1079`): construct empty strings with `String::default()` instead, so the zero value routes through the `Default` impl like every other default-constructed value in the codebase. - `no-test-in-python-docstrings` -- Docstring in non-test code mentions tests. Document what the code does for every caller; why a test needs it belongs in the test that relies on it. A name containing it (`test`, `ws-test-server`, `ET_TEST_X`) is allowed. - `no-test-in-rust-docs` -- Doc comment in non-test code mentions tests. Document what the item does for every caller; why a test needs it belongs in the test that relies on it. A name containing it (`test`, `ws-test-server`, `ET_TEST_X`) is allowed. - `no-uds-listener` -- HTTP servers here listen on TCP only. A Unix-domain-socket listener gives requests no peer address, and the handlers take the client address from the socket peer rather than from client-supplied `Forwarded` / `X-Forwarded-For` headers -- so `ws_handler` refuses every request arriving on one. Bind a TCP address instead. - `org-identity-lives-in-et-org` -- This constant spells out the organisation name or the crate prefix. Both belong to `et-org`, which is the one place either literal is written: take the value from `et_org::ORG`, `et_org::CRATE_PREFIX`, `et_org::NPM_SCOPE`, `et_org::REPOSITORY_URL` or `et_org::IMAGE_REGISTRY`, or build a longer string from them, rather than writing the name out again here. These strings are the same fact wearing different clothes -- the npm scope, the container registry, the repository URL and the crate prefix all move together if the project is renamed -- so a copy living outside `et-org` is a copy that gets missed. `et-org` writes the organisation exactly once and assembles the rest from it, which is what makes that rename a one-line change. A `concat!` cannot take a constant, so a call site that has to embed the URL at compile time uses `et_org::repository_url!()`; `et_org::org!()` is there for the same reason. This does not apply to a specific name that merely starts with the prefix. `et-ws-server` names one crate and `et-path-prefix-probe` names one test fixture; neither is the project's identity, and neither changes because the organisation did. Only the bare prefix `"et-"` and any string carrying the organisation name are covered. Tests are deliberately not exempt from the definition rule, but asserting a literal is not defining one: a test that pinned expected output against `et_org::NPM_SCOPE` would keep passing if that constant were wrong, so those assertions stay spelled out. - `prefer-self-mod-use-bare-chain` -- This file declares `mod $X;` and references it via `use $X::...`. Write `use self::$X::...` to make the local reference explicit. - `prefer-self-mod-use-bare-group` -- This file declares `mod $X;` and references it via `use $X::{...}`. Write `use self::$X::{...}` to make the local reference explicit. - `prefer-self-mod-use-pub-chain` -- This file declares `mod $X;` and re-exports it via `pub use $X::...`. Write `pub use self::$X::...` to make the local reference explicit. - `prefer-self-mod-use-pub-group` -- This file declares `mod $X;` and re-exports it via `pub use $X::{...}`. Write `pub use self::$X::{...}` to make the local reference explicit. - `prefer-self-mod-use-pubcrate-chain` -- This file declares `mod $X;` and re-exports it via `pub(crate) use $X::...`. Write `pub(crate) use self::$X::...` to make the local reference explicit. - `prefer-self-mod-use-pubcrate-group` -- This file declares `mod $X;` and re-exports it via `pub(crate) use $X::{...}`. Write `pub(crate) use self::$X::{...}` to make the local reference explicit. - `prefer-self-mod-use-pubsuper-chain` -- This file declares `mod $X;` and re-exports it via `pub(super) use $X::...`. Write `pub(super) use self::$X::...` to make the local reference explicit. - `prefer-self-mod-use-pubsuper-group` -- This file declares `mod $X;` and re-exports it via `pub(super) use $X::{...}`. Write `pub(super) use self::$X::{...}` to make the local reference explicit. - `python-process-control-except` -- A bare handler or BaseException catch can intercept shutdown and cancellation signals, including KeyboardInterrupt and SystemExit. Catch specific operational exceptions instead, or use straight-line cleanup ending in a bare raise. Check-only: choosing the correct exception types requires knowledge of the operation being protected. - `semgrep-path-anchored` (meta) -- Every semgrep `paths.include` / `paths.exclude` entry starts with `/`, anchoring it at the repository root. A pattern without one is easy to get wrong in either direction -- an exclusion that lost its slash silently matches nothing, while a bare `*.rs` matches at any depth only because of a rule nobody reading the config has to remember -- so the anchor is written out every time: `/**/*.rs` for any depth, `/verification/**` for one tree. This rule takes the patterns that match at any depth -- a bare name, or one opening with `**/` -- and its autofix rewrites each to the same match written from the root, `/**/`. A pattern with a `/` further in is already anchored at the root, and `semgrep-path-rooted` takes those. - `semgrep-path-rooted` (meta) -- A semgrep `paths.include` / `paths.exclude` entry with a `/` partway through matches from the repository root already, so it is written with the leading `/` that says so: `/verification/published/**`, not `verification/published/**`. The rule's autofix adds it. The patterns that match at any depth are `semgrep-path-anchored`'s, which writes them as `/**/`. - `use-mod-order` -- Order top-level items: external `use` (std / third-party) first, then `mod` / `pub mod` declarations, then local `use self::` / `crate::` / `super::`. So an external `use` must not appear after a `mod`, and a `self`/`crate`/`super` `use` must not appear before a `mod`. - `use-tracing-imports` -- Import what you use from `tracing` and call it by its bare name: `use tracing::{info, warn};` then `info!(...)`, `use tracing::instrument;` then `#[instrument(...)]`. A `tracing::` path at the use site hides the dependency from the file's imports, and mixing the two spellings in one crate makes the logging calls harder to find and change. - `verification-no-args` -- A generated deployment must not set a container's `args`. Configure the program through its environment instead -- every service here reads its settings through serde-env -- or, for an upstream image, run its own entrypoint and set whatever it reads from the environment. Kubernetes `args` replaces the image's `CMD` outright, so an `args` entry also silently discards the arguments the image was built to start with. - `write-ending-newline` -- A `write!` whose format string ends in `\n` is a `writeln!` written the long way: use `writeln!` and drop the final `\n`. This mirrors `DeepSource RS-C1005`. clippy's `write_with_newline` covers only a single trailing newline in a plain literal: it skips a string ending in `\n\n`, and one assembled with `concat!`, which are the cases this rule exists to catch. ## `cargo check` - `cargo-check` (`default` env; run by `check:rust`). ## `cargo clippy` - `cargo-clippy-check` (`default` env; run by `check:rust`). Repaired by `cargo-clippy-fix`. Config: `Cargo.toml`, `config/clippy.toml`. - `cargo-clippy-check-pkg` (`default` env; not run by `mise run check`) -- cargo clippy on one or more workspace packages (narrow inner-loop check). Config: `Cargo.toml`, `config/clippy.toml`. Rules, from `Cargo.toml`: - [`clippy::absolute_paths`][absolute_paths] -- allow, priority 4 - `clippy::all` -- deny, priority -1 - [`clippy::allow_attributes`][allow_attributes] -- deny, priority 4 - [`clippy::allow_attributes_without_reason`][allow_attributes_without] -- allow, priority 4 - [`clippy::arbitrary_source_item_ordering`][arbitrary_source_item] -- allow, priority 4 - [`clippy::as_conversions`][as_conversions] -- deny - [`clippy::blanket_clippy_restriction_lints`][blanket_clippy_restriction] -- allow, priority 8 - [`clippy::cast_lossless`][cast_lossless] -- deny - [`clippy::cast_possible_wrap`][cast_possible_wrap] -- deny - [`clippy::clone_on_ref_ptr`][clone_on_ref_ptr] -- deny, priority 4 - `clippy::complexity` -- deny, priority -1 - `clippy::correctness` -- deny, priority -1 - [`clippy::future_not_send`][future_not_send] -- deny, priority 4 - [`clippy::impl_trait_in_params`][impl_trait_in_params] -- deny, priority 4 - [`clippy::implicit_return`][implicit_return] -- allow, priority 8, opposite of `clippy::needless_return` - [`clippy::infinite_loop`][infinite_loop] -- deny - [`clippy::integer_division_remainder_used`][integer_division_remainder] -- deny, priority 8 - [`clippy::let_underscore_must_use`][let_underscore_must_use] -- deny, priority 4 - [`clippy::let_underscore_untyped`][let_underscore_untyped] -- deny, priority 4 - [`clippy::lint_groups_priority`][lint_groups_priority] -- allow, priority 8 - [`clippy::min_ident_chars`][min_ident_chars] -- deny, priority 4 - [`clippy::missing_docs_in_private_items`][missing_docs_in_private_items] -- allow, priority 2 - [`clippy::missing_errors_doc`][missing_errors_doc] -- allow, priority 4 - [`clippy::missing_inline_in_public_items`][missing_inline_in_public] -- allow, priority 4 - [`clippy::missing_panics_doc`][missing_panics_doc] -- allow, priority 4 - [`clippy::missing_trait_methods`][missing_trait_methods] -- allow, priority 4 - [`clippy::mod_module_files`][mod_module_files] -- allow, priority 4, opposite of `clippy::self_named_module_files` - [`clippy::module_name_repetitions`][module_name_repetitions] -- allow, priority 4 - `clippy::nursery` -- deny, priority -1 - [`clippy::pattern_type_mismatch`][pattern_type_mismatch] -- allow, priority 4 - `clippy::pedantic` -- deny, priority -1 - `clippy::perf` -- deny, priority -1 - [`clippy::pub_use`][pub_use] -- allow, priority 4 - [`clippy::pub_with_shorthand`][pub_with_shorthand] -- allow, priority 4, opposite of `clippy::pub_without_shorthand` - [`clippy::pub_without_shorthand`][pub_without_shorthand] -- deny, priority 4 - [`clippy::question_mark_used`][question_mark_used] -- allow, priority 8 - [`clippy::redundant_pub_crate`][redundant_pub_crate] -- allow, priority 4 - [`clippy::rest_pattern_accessible_field`][rest_pattern_accessible_field] -- allow, priority 4, opposite of `clippy::unneeded_field_pattern` - `clippy::restriction` -- deny, priority -1 - [`clippy::self_named_module_files`][self_named_module_files] -- deny, priority 4 - [`clippy::semicolon_if_nothing_returned`][semicolon_if_nothing_returned] -- deny, priority 4 - [`clippy::semicolon_outside_block`][semicolon_outside_block] -- allow, priority 4, opposite of `clippy::semicolon_inside_block` - [`clippy::separated_literal_suffix`][separated_literal_suffix] -- allow, priority 4, opposite of `clippy::unseparated_literal_suffix` - [`clippy::shadow_reuse`][shadow_reuse] -- allow, priority 4 - [`clippy::shadow_same`][shadow_same] -- allow, priority 4 - [`clippy::shadow_unrelated`][shadow_unrelated] -- allow, priority 4 - [`clippy::single_call_fn`][single_call_fn] -- deny, priority 4 - [`clippy::std_instead_of_alloc`][std_instead_of_alloc] -- allow, priority 8 - [`clippy::std_instead_of_core`][std_instead_of_core] -- allow, priority 8 - [`clippy::str_to_string`][str_to_string] -- allow, priority 4 - [`clippy::struct_excessive_bools`][struct_excessive_bools] -- allow, priority 8 - `clippy::style` -- deny, priority -1 - `clippy::suspicious` -- deny, priority -1 - [`clippy::too_many_arguments`][too_many_arguments] -- deny, priority 2 - [`clippy::type_complexity`][type_complexity] -- deny, priority 4 - [`clippy::unseparated_literal_suffix`][unseparated_literal_suffix] -- allow, priority 4 - [`clippy::unused_async`][unused_async] -- deny, priority 2 - [`dead_code`][dead_code] -- deny - [`let_underscore`][let_underscore] -- deny, priority -1 - [`macro_use_extern_crate`][macro_use_extern_crate] -- deny - [`non_ascii_idents`][non_ascii_idents] -- deny - [`private_bounds`][private_bounds] -- allow - [`private_interfaces`][private_interfaces] -- allow - [`redundant_imports`][redundant_imports] -- deny - [`redundant_lifetimes`][redundant_lifetimes] -- deny - [`rustdoc::private_intra_doc_links`][private_intra_doc_links] -- deny, priority 8 - [`trivial_numeric_casts`][trivial_numeric_casts] -- deny - [`unexpected_cfgs`][unexpected_cfgs] -- deny - [`unfulfilled_lint_expectations`][unfulfilled_lint] -- deny - [`unnameable_types`][unnameable_types] -- allow - [`unsafe_attr_outside_unsafe`][unsafe_attr_outside] -- deny - [`unsafe_code`][unsafe_code] -- deny - [`unsafe_op_in_unsafe_fn`][unsafe_op_in_unsafe_fn] -- deny - [`unused`][unused] -- deny, priority -1 - [`unused_results`][unused_results] -- deny [absolute_paths]: https://rust-lang.github.io/rust-clippy/stable/index.html#absolute_paths [allow_attributes]: https://rust-lang.github.io/rust-clippy/stable/index.html#allow_attributes [allow_attributes_without]: https://rust-lang.github.io/rust-clippy/stable/index.html#allow_attributes_without_reason [arbitrary_source_item]: https://rust-lang.github.io/rust-clippy/stable/index.html#arbitrary_source_item_ordering [as_conversions]: https://rust-lang.github.io/rust-clippy/stable/index.html#as_conversions [blanket_clippy_restriction]: https://rust-lang.github.io/rust-clippy/stable/index.html#blanket_clippy_restriction_lints [cast_lossless]: https://rust-lang.github.io/rust-clippy/stable/index.html#cast_lossless [cast_possible_wrap]: https://rust-lang.github.io/rust-clippy/stable/index.html#cast_possible_wrap [clone_on_ref_ptr]: https://rust-lang.github.io/rust-clippy/stable/index.html#clone_on_ref_ptr [future_not_send]: https://rust-lang.github.io/rust-clippy/stable/index.html#future_not_send [impl_trait_in_params]: https://rust-lang.github.io/rust-clippy/stable/index.html#impl_trait_in_params [implicit_return]: https://rust-lang.github.io/rust-clippy/stable/index.html#implicit_return [infinite_loop]: https://rust-lang.github.io/rust-clippy/stable/index.html#infinite_loop [integer_division_remainder]: https://rust-lang.github.io/rust-clippy/stable/index.html#integer_division_remainder_used [let_underscore_must_use]: https://rust-lang.github.io/rust-clippy/stable/index.html#let_underscore_must_use [let_underscore_untyped]: https://rust-lang.github.io/rust-clippy/stable/index.html#let_underscore_untyped [lint_groups_priority]: https://rust-lang.github.io/rust-clippy/stable/index.html#lint_groups_priority [min_ident_chars]: https://rust-lang.github.io/rust-clippy/stable/index.html#min_ident_chars [missing_docs_in_private_items]: https://rust-lang.github.io/rust-clippy/stable/index.html#missing_docs_in_private_items [missing_errors_doc]: https://rust-lang.github.io/rust-clippy/stable/index.html#missing_errors_doc [missing_inline_in_public]: https://rust-lang.github.io/rust-clippy/stable/index.html#missing_inline_in_public_items [missing_panics_doc]: https://rust-lang.github.io/rust-clippy/stable/index.html#missing_panics_doc [missing_trait_methods]: https://rust-lang.github.io/rust-clippy/stable/index.html#missing_trait_methods [mod_module_files]: https://rust-lang.github.io/rust-clippy/stable/index.html#mod_module_files [module_name_repetitions]: https://rust-lang.github.io/rust-clippy/stable/index.html#module_name_repetitions [pattern_type_mismatch]: https://rust-lang.github.io/rust-clippy/stable/index.html#pattern_type_mismatch [pub_use]: https://rust-lang.github.io/rust-clippy/stable/index.html#pub_use [pub_with_shorthand]: https://rust-lang.github.io/rust-clippy/stable/index.html#pub_with_shorthand [pub_without_shorthand]: https://rust-lang.github.io/rust-clippy/stable/index.html#pub_without_shorthand [question_mark_used]: https://rust-lang.github.io/rust-clippy/stable/index.html#question_mark_used [redundant_pub_crate]: https://rust-lang.github.io/rust-clippy/stable/index.html#redundant_pub_crate [rest_pattern_accessible_field]: https://rust-lang.github.io/rust-clippy/stable/index.html#rest_pattern_accessible_field [self_named_module_files]: https://rust-lang.github.io/rust-clippy/stable/index.html#self_named_module_files [semicolon_if_nothing_returned]: https://rust-lang.github.io/rust-clippy/stable/index.html#semicolon_if_nothing_returned [semicolon_outside_block]: https://rust-lang.github.io/rust-clippy/stable/index.html#semicolon_outside_block [separated_literal_suffix]: https://rust-lang.github.io/rust-clippy/stable/index.html#separated_literal_suffix [shadow_reuse]: https://rust-lang.github.io/rust-clippy/stable/index.html#shadow_reuse [shadow_same]: https://rust-lang.github.io/rust-clippy/stable/index.html#shadow_same [shadow_unrelated]: https://rust-lang.github.io/rust-clippy/stable/index.html#shadow_unrelated [single_call_fn]: https://rust-lang.github.io/rust-clippy/stable/index.html#single_call_fn [std_instead_of_alloc]: https://rust-lang.github.io/rust-clippy/stable/index.html#std_instead_of_alloc [std_instead_of_core]: https://rust-lang.github.io/rust-clippy/stable/index.html#std_instead_of_core [str_to_string]: https://rust-lang.github.io/rust-clippy/stable/index.html#str_to_string [struct_excessive_bools]: https://rust-lang.github.io/rust-clippy/stable/index.html#struct_excessive_bools [too_many_arguments]: https://rust-lang.github.io/rust-clippy/stable/index.html#too_many_arguments [type_complexity]: https://rust-lang.github.io/rust-clippy/stable/index.html#type_complexity [unseparated_literal_suffix]: https://rust-lang.github.io/rust-clippy/stable/index.html#unseparated_literal_suffix [unused_async]: https://rust-lang.github.io/rust-clippy/stable/index.html#unused_async [dead_code]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#dead-code [let_underscore]: https://doc.rust-lang.org/rustc/lints/groups.html [macro_use_extern_crate]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#macro-use-extern-crate [non_ascii_idents]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#non-ascii-idents [private_bounds]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#private-bounds [private_interfaces]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#private-interfaces [redundant_imports]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#redundant-imports [redundant_lifetimes]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#redundant-lifetimes [private_intra_doc_links]: https://doc.rust-lang.org/rustdoc/lints.html#private_intra_doc_links [trivial_numeric_casts]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#trivial-numeric-casts [unexpected_cfgs]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs [unfulfilled_lint]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unfulfilled-lint-expectations [unnameable_types]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#unnameable-types [unsafe_attr_outside]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#unsafe-attr-outside-unsafe [unsafe_code]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#unsafe-code [unsafe_op_in_unsafe_fn]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#unsafe-op-in-unsafe-fn [unused]: https://doc.rust-lang.org/rustc/lints/groups.html [unused_results]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#unused-results ## `cargo deny` - `cargo-deny-check` (`default` env; not run by `mise run check`) -- Audit dependencies with cargo-deny (advisories, bans, licenses, sources). Config: `config/deny.toml`. ## `cargo doc` - `cargo-doc-check` (`default` env; run by `check:rust`) -- Type-check rustdoc with -D warnings to fail on any doc issues. ## `cargo fmt` - `cargo-fmt-check` (`default` env; run by `check:rust`). Repaired by `cargo-fmt`. Config: `.rustfmt.toml`. ## `cargo hack` - `cargo-hack-check` (`default` env; not run by `mise run check`) -- cargo clippy every crate's feature powerset. ## `cargo metadata` - `cargo-clippy-wasm-check` (`default` env; run by `check:rust`) -- cargo clippy the browser and WASI crates on their wasm targets. ## `cargo shear` - `cargo-shear-check` (`default` env; run by `check:rust`) -- Find unused and misplaced Cargo dependencies with cargo-shear. Repaired by `cargo-shear-fix`. ## `cargo unmaintained` - `cargo-unmaintained-check` (`default` env; not run by `mise run check`) -- Flag dependencies whose upstream repo is archived or unmaintained. - `cargo-unmaintained-check-pkg` (`default` env; not run by `mise run check`) -- Check one dependency with cargo-unmaintained (cheap re-check of a single finding). ## `clang-format` - `clang-format-check` (`zig` env; run by `check:zig`) -- Check C/C++ source formatting (clang-format). Repaired by `clang-format`. Config: `config/clang-format.yaml`. ## `clang-tidy` - `clang-tidy-check` (`zig` env; run by `check:zig`) -- Lint C/C++ sources (clang-tidy). Repaired by `clang-tidy-fix`. Config: `config/clang-tidy.yaml`. ## `conftest` - `checks-md-check` (`default` env; run by `check:rust`) -- Fail if CHECKS.md was last generated more than 7 days ago (run `mise run gen:checks`) (conftest). Config: `config/conftest/policy`. - `conftest-check-branch-size` (`default` env; run by `conftest-check`) -- Fail when the branch modifies more files than CodeRabbit reviews in one pull request (conftest). Config: `config/conftest/policy`. - `conftest-check-checks-badge` (`default` env; run by `conftest-check`) -- Check the README checks badge keeps step with CHECKS.md's rules count (conftest). Config: `config/conftest/policy`. - `conftest-check-dockerfile` (`default` env; run by `conftest-check`) -- Cross-check Dockerfiles against mise [tools] pins + heredoc body-first-line invariants. Config: `config/conftest/policy`. - `conftest-check-generated-trees` (`default` env; run by `conftest-check`) -- Cross-check config/generated-trees.toml against the linter configs it governs (conftest). Config: `config/ast-grep/rules`, `config/conftest/policy`, `config/generated-trees.toml`, `config/jscpd.json`, `config/pyrefly.toml`, `config/semgrep`. - `conftest-check-jscpd` (`default` env; run by `conftest-check`) -- Check the jscpd baseline has not grown (conftest). Config: `config/conftest/policy`, `config/jscpd-baseline.json`. - `conftest-check-mise-templates` (`default` env; run by `conftest-check`) -- Check no mise config value contains Tera's comment opener (conftest). Config: `config/conftest/policy`. - `conftest-check-no-exemptions` (`default` env; run by `conftest-check`) -- Check the rules held to every file carry no path exemption, and their inline guard (conftest). Config: `config/ast-grep/rules`, `config/conftest/policy`, `config/semgrep`. - `conftest-check-policy-pairs` (`default` env; run by `conftest-check`) -- Check every conftest policy has a test file beside it (conftest). Config: `config/conftest/policy`. - `conftest-check-policy-tests` (`default` env; run by `conftest-check`) -- Run the Rego unit tests beside the conftest policies (conftest verify). Config: `config/conftest/policy`. - `conftest-check-toml` (`default` env; run by `conftest-check`) -- Run conftest OPA/Rego policies over the TOML config + lock files. Config: `config/conftest/policy`, `config/deny.toml`, `config/pyrefly.toml`, `config/upstream-cache/data.toml`. - `conftest-check-yaml` (`default` env; run by `conftest-check`) -- Run conftest OPA/Rego policies over the GitHub Actions YAML, and the backslash ban over all YAML. Config: `config/conftest/policy`. Rules, from `config/conftest/policy`: - `advisories` (`config/conftest/policy/advisories/advisories.rego`): - Every advisory ignore must carry a `reason = "expires YYYY-MM-DD"` review date. - Nanoseconds in a day, added to the parsed midnight so an entry survives the whole of its review date. - Every advisory ignore expires on one date, the next milestone review, which the operator sets. - `branch_size` (`config/conftest/policy/branch_size/branch_size.rego`): - A branch may modify at most `max_files` files, or CodeRabbit skips its pull request's review. - `cargo` (`config/conftest/policy/cargo/cargo.rego`): - The root Cargo.toml must not depend on a crate this repo has banned. - Member crates: no path deps, no wildcard versions, no inline git deps. - A member crate must not take a dependency at a wildcard version. - A member crate must not take an inline git dependency. - Member [dependencies]/[dev-dependencies] must inherit via workspace = true so pins stay in [workspace.dependencies]. - A crate with a [lib] must disable the doctest harness and must not rename the lib. - A member crate's [lib] must not rename the library away from the package name. - Every member must inherit the workspace lint tables. - Every member crate must be registered in the root [workspace].members. - Shared [package] metadata must be inherited from [workspace.package] via `.workspace = true`. - Crate names are namespaced: "edge-toolkit" or "et-" for normal crates, "int-" marking an internal one. - `publish` is stated in every manifest rather than left to cargo's implicit default. - The "int-" marker and publishability are the same fact, so the name and the flag must agree both ways. - A crate without the int- marker must set publish = true. - A path dependency's `version` must match the version its crate actually declares. - A root workspace dependency must be pinned to major.minor, not a patch version, unless it is excepted. - The exception map is a two-way contract. - crates.io rejects an upload whose manifest carries no description, so every publishable crate has one. - An empty `features = []` on a dependency is pointless noise -- drop it. - A member crate's feature must not share its name with one of its dependencies. - No manifest may depend on a `cargo_*` / `cargo-*` crate (`cargo_metadata`, `cargo_toml`, ...). - Crates log through `tracing`, so none may depend on the `log` facade directly. - tokio's `full` feature is banned on every dependency spec, the workspace table included. - The root `tracing` dependency must enable `log`. - A crate that declares a feature must publish the feature list on docs.rs through `document-features`. - Dependency overrides ([patch]/[replace]) belong in the root manifest, not a member crate. - `dependencies` (`config/conftest/policy/cargo/dependencies.rego`): - No crate may depend on an internal one. - A standalone crate must not depend on any of this repo's `et-` crates. - No crate under services/ may depend on clap. - `checks_badge` (meta) (`config/conftest/policy/checks_badge/checks_badge.rego`): - CHECKS.md must record its rules count, which the badge is held to. - README.md must define the checks badge. - The checks badge records the rules count rounded down to a multiple of 20, so it moves with every 20 rules added. - `checks_md` (`config/conftest/policy/checks_md/checks_md.rego`): - CHECKS.md must say when it was generated, which only the generator writes. - A document stamped more than once was not written by the generator, which stamps it exactly once. - A stamp the shape allows but the calendar does not, such as 2026-02-30, was written by hand and has no age. - The document is regenerated at least once every `max_age_days` days. - The generator stamps today's UTC date, so a later one was written by hand. - `checksums` (`config/conftest/policy/checksums/checksums.rego`): - Every `_asset` var in the mise configs must have an [asset] table in config/upstream-cache/data.toml. - Every [asset] table in config/upstream-cache/data.toml must be referenced by a mise `_asset` var. - Per-entry shape check on each asset table. - Every recorded asset must name its `upstream` project URL. - Every recorded asset must carry its `license` as an SPDX expression. - Every recorded asset must carry a `sha256`, left empty only while bootstrapping. - `pyrefly_python_sync` (`config/conftest/policy/cross/pyrefly_python_sync.rego`): - pyrefly must pin the Python version it type-checks against. - pyrefly's pinned Python version must equal the mise `python` pin, the version every lane installs. - `wasm-bindgen-sync` (`config/conftest/policy/cross/wasm-bindgen-sync.rego`): - The mise wasm-bindgen pin must match the wasm-bindgen version Cargo.lock resolves. - `dockerfile` (`config/conftest/policy/dockerfile/dockerfile.rego`): - A version a Dockerfile hard-codes must match the [tools] pin for the same tool. - Every pipx:* tool in an in-scope config must be in Dockerfile.nanoserver's MISE_DISABLE_TOOLS. - RUN heredocs must invoke `bash` and use a QUOTED delimiter (`RUN bash <<'EOF'`). Two-part rule. - A RUN heredoc must quote its delimiter so bash, not the outer shell, does the expansion. - A Dockerfile `apt-get install` must pass --no-install-recommends on the same line. - A Dockerfile must use `apt-get`, never `apt`. - A renamed or removed ARG would leave the comparison undefined, which reads as a pass; fail instead. - [bootstrap.packages] must list every package the Dockerfile ARGs install. - [bootstrap.packages] must not list a package the Dockerfile ARGs do not install. - Dockerfile.nanoserver is exempt: it deliberately loads a subset, because pipx tools cannot run on Nano Server. - Every other Dockerfile's COMMON_PACKAGES must be drawn from the root Dockerfile's list. - A Dockerfile's MISE_VERSION must match .mise/config.toml's min_version. - `dockerfile_heredoc` (`config/conftest/policy/dockerfile_heredoc/dockerfile_heredoc.rego`): - A RUN heredoc's first body line must be `set -euo pipefail`. - `dockerfile_stages` (`config/conftest/policy/dockerfile_stages/dockerfile_stages.rego`): - A `--from=` naming no stage declared before it would pull an image of that name. - `generated_trees` (meta) (`config/conftest/policy/generated_trees/generated_trees.rego`): - Every config a generated tree requires must carry that tree's exclusion pattern. - Reverse direction, matched on the tree prefix rather than on the exact pattern. - A `required_in` entry that no [anchor] key covers would make the forward rule above vacuous. - `gha` (`config/conftest/policy/gha/gha.rego`): - Every workflow must set the default run shell so steps run in bash on every runner without a per-step `shell:`. - Steps must not override the shell -- rely on the workflow default. - Every workflow must declare MISE_ENV at the workflow level so the set of loaded language envs is visible at a glance. - The MISE_ENV VALUE must be the full guest-language set. - `DOCKER_BUILDKIT=1` must not be set in the docker-windows workflow. - The docker-windows workflow must not set DOCKER_BUILDKIT at job scope either. - Nor at step scope. - A workflow's trigger paths must cover every local action it uses, so an edit to one re-runs it. - A workflow step's `apt-get install` must pass --no-install-recommends on the same line. - A workflow step must use `apt-get`, never `apt`. - Every actions/upload-artifact step must set with.if-no-files-found: error. - Scalar lists: `matrix.: [a, b, c]`. - `include:` entries, which is where the per-combination keys live. - `gha_action` (`config/conftest/policy/gha_action/gha_action.rego`): - Composite-action `run:` steps require an explicit `shell:`. - Every actions/upload-artifact step must set with.if-no-files-found: error. - `gha_combined` (`config/conftest/policy/gha_combined/gha_combined.rego`): - Every workflow must trigger on pull_request so PR CI runs it when relevant code changes. - Every workflow must support manual workflow_dispatch: re-running a failed/flaky run shouldn't need a no-op commit. - When a workflow scopes its pull_request trigger by `paths`, the workflow's own YAML must be in that list. - `gha_mise` (`config/conftest/policy/gha_mise/gha_mise.rego`): - Every setup-dotnet `dotnet-version` must equal the mise `dotnet` pin. - Any workflow string embedding a mise install path must track the matching [tools] pin. - A workflow's env.MISE_ENV must match .mise/config.toml's ALL_LANGS. - `gha_uses` (`config/conftest/policy/gha_uses/gha_uses.rego`): - A step must not pass an input that the local action it calls does not declare. - Matched with `contains` rather than `startswith`, since the root need not begin the string. - `jscpd` (meta) (`config/conftest/policy/jscpd/jscpd.rego`): - Module-scope sprintf templates, each on its own line so the source stays inside the line limit. - declared_clones may sit at most max_slack above the baseline, so progress gets banked. - `mise` (`config/conftest/policy/mise/mise.rego`): - A task `run` must be a string, not an array. - A multiline `run` must use `shell = "{{ vars.task_shell }}"`. - Task descriptions must be single-line. - `compgen` is a bash builtin that busybox-w32 ash (Nano Server's shell) does not provide. - A task `run` must not embed version-like text (an `X.Y.Z` literal), comments included. - A `cargo:` tool must not build from source unless allowlisted or os-scoped to a second-tier platform. - `ubi:` is deprecated; the `http:` backend replaces it. - An `http:` tool must not fetch from a forge URL; use the github: backend instead. - No per-platform URL of an `http:` tool may point at a forge either. - A tool must not be os-scoped unless allowlisted, since every tool must work on every OS. - A mise install path that a config string embeds must name the version [tools] pins. - python must be pinned to a full version triple (X.Y.Z), not a minor alias. - Each OS's preinstall task must read its package lists from the Dockerfile ARGs. - Every usage arg a task declares must be read by its run body. - The uppercase spelling is a no-op everywhere except Windows, so it is rejected on sight. - `mise_lock` (`config/conftest/policy/mise_lock/mise_lock.rego`): - A tool locks a glibc Linux build without an allowlist entry. - An allowlist entry names a tool that no longer locks a glibc build anywhere. - `no_exemptions` (meta) (`config/conftest/policy/no_exemptions/no_exemptions.rego`): - An unexemptable semgrep rule may not set `paths`, which is how semgrep narrows the files a rule scans. - An unexemptable ast-grep rule may not set `files` or `ignores`, the two ways ast-grep narrows a rule. - The inline guard must name every unexemptable rule, or suppressing one added later would go unflagged. - The inline guard must exist, since without it a suppression comment is an exemption nothing reports. - `no_trailing_backslash` (`config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego`): - Flag any string leaf containing `\` followed by `\n` (LF), reporting its path key in the message. - `policy_pairing` (meta) (`config/conftest/policy/policy_pairing/policy_pairing.rego`): - Every policy must have a test file beside it, or a reasoned untested_policy entry. - The map is a two-way contract, the same way a lint suppression is. - An entry naming a policy that is no longer there has outlived what it described. - `pyproject` (`config/conftest/policy/pyproject/pyproject.rego`): - A `path = ".."` uv source points at the parent directory, which isn't a package -- almost always a mistake. - A pyproject's uv_build requirement must pin the uv version mise installs. - `tera_comment` (`config/conftest/policy/tera_comment/tera_comment.rego`): - A mise config value must not contain `{#`, which Tera reads as the start of a comment. ## `cpplint` - `cpplint-check` (`zig` env; run by `check:zig`) -- Lint C/C++ sources for Google C++ style (cpplint). ## `dart` - `dart-check` (`dart` env; run by `check:dart`). Repaired by `dart-fix`. - `dart-fmt-check` (`dart` env; run by `check:dart`). Repaired by `dart-fmt`. ## `dotnet` - `dotnet-check` (`dotnet` env; run by `check:dotnet`) -- Check dotnet C# formatting. Repaired by `dotnet-fmt`. ## `dprint` - `dprint-check` (`default` env; run by `check:rust`). Repaired by `dprint-fmt`. Config: `.dprint.jsonc`, `config/dprint.jsonc`. ## `editorconfig-checker` - `editorconfig-check` (`default` env; run by `check:rust`). Config: `.editorconfig`. ## `et-int-gen` - `gen-help-all` (`default` env; run by `check:rust`) -- Regenerate HELP.md for every binary crate using clap or reading serde-env config. - `gen-help-check` (`default` env; run by `check:rust`) -- Fail if any checked-in HELP.md is stale vs its clap tree or env config (run `mise run gen-help-all`). ## `et-int-repo-check` - `repo-check` (`default` env; run by `check:rust`) -- Repository-wide checks that need to read git itself (commit hashes, ...). ## `flawfinder` - `flawfinder-check` (`zig` env; run by `check:zig`) -- Scan C/C++ sources for risky C APIs (flawfinder security lint). ## `gitleaks` - `gitleaks-check` (`default` env; run by `check:rust`) -- Scan the working tree for committed secrets with gitleaks. Config: `config/gitleaks.toml`. ## `gocloc` - `sloc-crosscheck` (`coverage` env; not run by `mise run check`) -- Re-count sloc's file lists with gocloc, an independent implementation of cloc's rules. ## `hadolint` - `hadolint-check` (`default` env; run by `check:rust`) -- Lint the Dockerfiles with hadolint. Config: `config/hadolint.yaml`. ## `jscpd` - `jscpd-check` (`default` env; run by `check:rust`) -- Detect copy/pasted code across every language (jscpd). Config: `config/jscpd-baseline.json`, `config/jscpd.json`. ## `kubeconform` - `kubeconform-check` (`default` env; run by `check:rust`) -- Validate the generated Kubernetes manifests against the Kubernetes API schemas. ## `ls-lint` - `ls-lint-check` (`default` env; run by `check:rust`) -- Lint file and directory naming conventions (ls-lint). Config: `config/ls-lint.yaml`. ## `lychee` - `link-check` (`default` env; run by `check:rust`) -- Check that URLs in prose, source and config files are reachable (network). Config: `config/lychee.toml`. ## `osv-scanner` - `osv-scanner-check` (`default` env; not run by `mise run check`). Config: `config/osv-scanner.toml`. ## `oxfmt` - `oxfmt-check` (`js` env; run by `check:js`) -- Check JS/TS formatting (oxfmt). Repaired by `oxfmt-fmt`. Config: `config/oxfmtrc.jsonc`. ## `oxlint` - `oxlint-check` (`js` env; run by `check:js`) -- Lint JavaScript/TypeScript sources (oxlint). Repaired by `oxlint-fix`. Config: `config/oxlintrc.jsonc`, `config/tsconfig.json`. ## `pyrefly` - `pyrefly-check` (`python` env; run by `check:python`). Config: `config/pyrefly.toml`. ## `regal` - `regal-check` (`default` env; run by `check:rust`) -- Lint Rego policy files with Regal (style, idiomatic, bugs). Repaired by `regal-fix`. Config: `config/conftest/policy`, `config/regal.yaml`. ## `roslynator` - `roslynator-check` (`dotnet` env; run by `check:dotnet`) -- Analyze C# sources (Roslynator). Repaired by `roslynator-fix`. ## `ruff` - `ruff-check` (`python` env; run by `check:python`). Repaired by `ruff-fix`. Config: `ruff.toml`. - `ruff-fmt-check` (`python` env; run by `check:python`). Repaired by `ruff-fmt`. Config: `ruff.toml`. ## `ryl` - `ryl-check` (`default` env; run by `check:rust`) -- Lint YAML with ryl (a yamllint-compatible Rust linter). Config: `config/ryl.toml`. ## `semgrep` - `semgrep-check` (`default` env; run by `check:rust`) -- Run Semgrep code and configuration rules. Repaired by `semgrep-fix`. Config: `.semgrepignore`, `config/semgrep`. - `semgrep-check-rule-tests` (`default` env; run by `check:rust`) -- Run the Semgrep rule tests against their annotated cases (semgrep --test). Config: `.semgrepignore`, `config/semgrep`, `config/semgrep-tests`. Rules, from `config/semgrep`: - `anyhow-only-in-error-rs` -- `anyhow` may only be named in an `error.rs`, in a `#[from]` variant of the crate's error enum. Domain code returns that enum and relies on `?`, so an `anyhow::Error` never reaches a public signature or a match arm. If a foreign API hands you one here, add the `#[from]` variant to this crate's `error.rs` and let `?` do the conversion. - `bare-workspace-inline-dep` -- Use the dotted form `dep.workspace = true` instead of the inline table `dep = { workspace = true }`. (Inline tables with extra keys, e.g. `dep = { workspace = true, features = [...] }`, are fine.) - `cargo-feature-needs-doc-comment` -- Document every cargo feature with a `##` comment on the lines directly above it. `document-features` renders those comments as the crate's feature list on docs.rs, which is where users choose features, so an undocumented feature is one they cannot learn about without reading the manifest. A plain `#` comment is not rendered; turn it into `##` user-facing prose, and mark a feature only the repo's own tooling uses with `Internal:`. - `cargo-table-order` -- Order a crate manifest's tables as every other crate in the workspace does: `[package]` and its `[package.metadata.*]` subtables, `[lib]`, `[[bin]]`, `[[example]]`, `[[test]]`, `[[bench]]`, `[features]`, `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]`, then `[lints]`. A `target.` dependency table follows its plain one. One order means a reader finds a table without scanning for it, and a diff that adds one lands in the same place in every crate. Move the reported table to its slot. - `comment-summary-line` -- A multi-line `#` comment block must open with a single summary line ending in a full stop (PEP 257 style). The summary describes the whole block in one sentence; continuation lines follow. End the first comment line with a `.`. - `comment-summary-line-c` -- A multi-line C-style comment (a `/* */` block or a `//` run) must open with a single summary line ending in a full stop (PEP 257 style). The summary describes the whole block in one sentence; continuation lines follow. End the first comment line with a `.`. - `deepsource-code-must-name-its-analyzer` -- Write a DeepSource finding as `DeepSource RS-W1079`, naming the analyzer in front of the code. A bare code is unreadable to anyone who does not already know whose it is: `RS-`, `JS-` and `CXX-` look like they could belong to any of the analyzers this repo runs, and a search for one turns up nothing unless the searcher guesses the vendor first. The exception is a `skipcq:` pragma, whose grammar DeepSource defines -- leave the code bare there, and let the surrounding comment carry the reason. - `dockerfile-heredoc-set-euo-pipefail` -- Dockerfile RUN heredocs must begin with `set -euo pipefail` as the first body line. BuildKit's default heredoc shell carries neither -e nor -o pipefail (and dash on Debian/Ubuntu doesn't support -o pipefail at all), so the strict-mode invariant has to be re-declared per body. Paired with the policy that requires `RUN <`. A bare `mkdir`/`cp`/`rm`/... runs whatever the host ships -- BSD on macOS, GNU on Linux, busybox or Git Bash on Windows -- so flags and behaviour differ per machine, and the task depends on a tool mise does not pin. A command that is run by another shell (inside a container or pod) is a genuine exception: suppress it on that line with a `nosemgrep` comment saying where it runs. - `module-description-length` -- A ws-module description is the module-dropdown label on the ws-server index page; keep it at most 40 characters so the dropdown stays readable. - `multiline-literal-string-needs-backslash` -- Write a multi-line string without a backslash as `"""..."""`, not `'''...'''`. A TOML literal string exists to keep backslashes verbatim (Windows paths, regex escapes); without one it only makes this value differ from the `"""` bodies around it, and the next reader looks for an escape that is not there. - `multiline-string-closes-on-own-line` -- Close a multi-line `"""` (or `'''`) string with the delimiter alone on its own line, not on the end of its last line. Every multi-line value then ends the same way and appending a line never means editing the one before it. In a Tera value, end the last tag with `-%}` (e.g. `{%- endif -%}`) so the newline before the delimiter is trimmed and the rendered value is unchanged. - `multiline-task-run-needs-bash-shell` -- A mise task with a multiline `run` must be immediately followed by `shell = "{{ vars.task_shell }}"`. Put `shell` on the line right after the closing `"""`/`'''`, so a failing command fails the task instead of being masked. `task_shell_trace` is also accepted (xtrace, used on the Windows OS-specific preinstall for failure-diagnosis transcripts); `task_shell_plain` is not, since it carries no `-e`. bash is installed before any task on Windows (see preinstall), so even the setup tasks can use it. - `no-action-validator-mentions` -- Do not name the workflow schema validator outside the files that install and run it. Whether a workflow key is spelled a certain way to keep a validator happy is not what a reader of that workflow needs; say what the key does. - `no-actionlint-mentions` -- Do not name the GitHub Actions linter outside the files that install and run it. A workflow comment crediting it for a shell quoting or expression change says nothing a reader of the workflow can use. - `no-ast-grep-mentions` -- Do not name the structural search tool outside its own ruleset and the configs that scope it. Pointing at one of its rules from the code that rule governs duplicates the rule's own message, which is where the explanation belongs and where it stays correct. - `no-build-task-name-mentions` -- Do not name a module build task outside the images, jobs and READMEs that run it. A skip message telling a developer which build to run is the exception, and the reason it works is that it is addressed to someone at a terminal; a comment in the module's own source is not. - `no-cargo-deny-mentions` -- Do not name the dependency auditor outside its config and the job that runs it. A dependency is chosen or avoided for a licensing or advisory reason, which stands on its own; the auditor that would have noticed is not part of it. - `no-cargo-unmaintained-mentions` -- Do not name the unmaintained-crate scanner outside its ignore list and the job that runs it. Its ignore entries need the tool's own heuristics explained; nothing else in the tree does. - `no-cat-heredoc` -- Writing a file by piping a heredoc through `cat` is banned in this repo. A program embedded that way is invisible to every checker: it is a string as far as the surrounding file is concerned, so nothing lints it, formats it, or reports it in a diff as the language it is written in, and the quoting rules of two layers have to be satisfied before its first byte reaches the interpreter. Put the content in its own file beside whatever runs it -- `.mise/` for a mise task body -- named with the extension of whatever reads it (`.awk`, `.jq`, `.sh`), and point the tool at that path instead. It then reads as the program it is, and a path is all the caller needs. - `no-changelog-links` -- CHANGELOG.md must not contain links. What an entry would link to does not exist when it is written: the version tag a compare or release link names is only created after the release merges, so the link 404s -- and fails the link check -- in exactly the change that adds it. Write references as plain text instead: a pull request as `#123`, a version as the bare `[0.3.0]` heading, and a project by its name. - `no-check-task-name-mentions` -- Do not name a check or formatter task outside its own config, the images and jobs that run it, and the docs that say when to. Saying code is written a certain way because one of these would object describes the check rather than the code, and it is the first thing to rot: the task gets renamed, folded into an aggregate, or its rule moves, and the comment keeps asserting the old arrangement. State the constraint. - `no-clang-format-mentions` -- Do not name the C formatter outside its own config. Formatted code carries no evidence of who formatted it, and a comment claiming a brace or a blank line is the formatter's doing is unverifiable noise. - `no-clang-tidy-mentions` -- Do not name the C static analyzer outside its config. Its suppression marker is a bare `// NOLINT` naming the check, which is all a reader needs -- spelling out the analyzer beside it adds nothing and dates the comment to whichever analyzer happened to run that week. - `no-cmd-scripts` -- A `.cmd` file is banned everywhere in this tree, with no exemption at all -- not in `.mise/`, and not under a `.venv/` the way its sibling extensions are. The reasoning is the same one that bans `.bat` and `.ps1`: a Windows-only shell file is exercised on one of five platforms and silently untested on the other four. It gets no carve-out because nothing generates one -- a virtualenv writes `activate.bat` and `activate.ps1` but never a `.cmd` -- so every `.cmd` in the tree is something a person wrote by hand, and belongs in a task body under `.mise/` or its own directory under `utilities/` instead. - `no-codacy-mentions` -- Do not name this analyzer outside its own config and the few files it excludes by path, where the header is the exclusion's canonical home. It runs after a push, so a source comment about it describes something the reader cannot see, run, or check; state the property the code actually has. - `no-codecov-mentions` -- Do not name the coverage service outside the jobs that upload to it. A test exists to pin behaviour, and saying it was added for a coverage percentage tells the next reader to delete it once the number moves. - `no-comment-in-dev-dependencies` -- Do not comment entries in a `[dev-dependencies]` section. What a test dependency is for belongs in the test that uses it, where the reader is already looking; a note here duplicates that and goes stale the moment the test changes. Delete the comment, and put anything genuinely worth saying next to the code that depends on it. - `no-conftest-mentions` -- Do not name the policy runner outside its own policy tree and the tasks that feed it. A config entry is shaped by what the config has to express; a note that some policy would otherwise object belongs in that policy's own deny message, where it is shown to the person who trips it. - `no-coverage-task-name-mentions` -- Do not name a coverage task outside the pipeline that runs it. Where one is a genuine precondition -- it starts the server a test needs, or it is the trusted caller that makes an argv-derived path safe -- the name is the precondition and belongs there. A test's own assertions never are. - `no-cpplint-mentions` -- Do not name the C++ style checker outside the configs that run it or defer to it. Its suppression marker carries the rule name already, and nothing else in a C file is the checker's business. - `no-curl-pipe` -- Piping `curl` into another command is banned in this repo. `curl ... | sh` executes an unreviewed remote script outright: whoever controls the URL, or anyone able to substitute the response, chooses what runs, and a truncated download is indistinguishable from a good one. Download to a file first, verify it with a pinned `sha256sum -c` or a signature, and only then use it. `| tar` is banned too, and is not the safe exception it looks like: `tar --to-command=` runs every archive member through that command, and `-I` / `--use-compress-program` likewise hand an arbitrary program the stream, so a piped archive can execute code just as a piped script can. Fetch, verify, then extract. - `no-deepsource-mentions` -- Do not name this analyzer outside its own config, the pragmas it reads, and a reference to one of its codes. Naming it in front of a code -- `DeepSource RS-W1079` -- is required, because the code alone does not say whose it is. Naming it anywhere else describes the tool's reaction rather than the code. - `no-dprint-mentions` -- Do not name the markdown/YAML/JSON formatter outside its config and the configs held to agree with it. Where a generator has to emit already-formatted output, say which shape the output must have; naming the formatter dates the comment to a config the generator never reads. - `no-editorconfig-check-mentions` -- Do not name the editorconfig checker outside the task that runs it. Write "the editorconfig line length" when a comment genuinely has to mention the limit -- that names the constraint and its source, which is what a reader can act on. - `no-flawfinder-mentions` -- Do not name the C/C++ security scanner except in the `flawfinder: ignore` pragma, which only works spelled out on the line it covers. Its justification belongs on that same line and says why the call is safe. - `no-generator-task-name-mentions` -- Do not name a generator task outside the generator, the files it writes, and the docs that say when to run it. A generated file's own header is where the name belongs, because a reader who must not hand-edit the file needs the command that rebuilds it. Elsewhere, "the drift check fails" says everything the reader can act on, and survives the task being renamed or folded into an aggregate. - `no-gitleaks-mentions` -- Do not name the secret scanner outside its config and the `gitleaks:allow` marker it reads. A credential is kept out of a tracked file because it is a credential, not because a scanner would find it. - `no-hadolint-mentions` -- Do not name the Dockerfile linter outside its config, its `hadolint ignore=` pragmas, and the code that emits them. Say what the instruction does and why it is written that way; a pragma already carries the code it suppresses. - `no-jscpd-mentions` -- Do not name the duplication checker outside its config, its baseline, and the policy that ratchets them. A reported clone is a request to remove duplication: factor the shared part out so it exists once. Where a clone is genuinely irreducible -- the same few lines every caller must write verbatim -- leave the code plain and raise it, rather than annotating the source with the tool's name. - `no-kubeconform-mentions` -- Do not name the manifest schema validator outside the job that runs it. A manifest field is set because Kubernetes requires or honours it, which is what the comment beside it should say. - `no-line-length-in-comment` -- Do not hardcode the line-length number in a comment. Say "editorconfig line length" when the comment genuinely needs the constraint, otherwise drop the mention entirely -- the number drifts and adds noise. - `no-ls-lint-mentions` -- Do not name the file-naming linter outside its config and the rule that governs it. A file or directory is named for what it holds; the checker that would object to another name is not part of that. - `no-lychee-mentions` -- Do not name the link checker outside its config and the task that runs it. A link is written to point somewhere useful, and a note that the checker accepts or skips it will outlive the reason it did. - `no-maintainer-task-name-mentions` -- Do not name an install, prefetch or asset-publishing task outside the jobs, images and asset tables that invoke it. These run on a maintainer's machine or a runner, never from the code, so a source file naming one is describing an operation it has no part in. - `no-nextest-mentions` -- Do not name the test runner outside its config and the jobs that invoke it. A test asserts what the code must do, and how it happens to be scheduled is the runner's business rather than the test's -- unless the schedule is load-bearing, which is a constraint worth stating and worth allowlisting for. - `no-non-ascii` -- Non-ASCII character. Keep source, config, docs and generated output ASCII-only: use `--` for an em-dash, `->` for an arrow, `...` for an ellipsis, `<=`/`!=`/`^2` for math glyphs, and straight quotes for curly quotes. ASCII keeps terminals, log scrapers, and `grep` reading the same bytes the editor shows. Nothing is exempt: when a generator emits the character, fix the generator or the task that runs it. - `no-observability-task-name-mentions` -- Do not name an observability task outside the configs it loads and the things that launch it. A collector config naming the task that reads it is how a reader finds its way in; a comment elsewhere saying some value exists for one of these tasks is not. - `no-os-scope-in-platform-mise-config` -- Do not set `os = [...]` in a platform-specific mise config. The file is only loaded on its own platform, so the key restates what the filename already says, and a value naming a different OS silently disables the entry everywhere. Drop the key; a tool that genuinely needs scoping to a subset of platforms belongs in a cross-platform config instead. - `no-osv-scanner-mentions` -- Do not name the vulnerability scanner outside its config, the job that runs it, and the ignore lists that have to say which scanner honours which id. Everywhere else, describe the dependency, not the scan. - `no-oxfmt-mentions` -- Do not name the JS formatter outside its config and the config held to agree with it. When a statement has to be split to keep both formatters happy, the fix is in the source -- a shorter line, an extracted local -- and the split stands on its own without a comment naming either tool. - `no-oxlint-mentions` -- Do not name the JS linter outside its config. A `skipcq` or eslint-style disable comment already carries the rule it silences plus the reason it is safe; adding the linter's name tells the reader nothing more. - `no-pyrefly-mentions` -- Do not name the Python type checker outside its config and the rules that protect what it can see. An annotation states the type the code requires; that it also satisfies a checker follows, and need not be written down. - `no-python-outside-modules` -- Python outside the module trees is banned. It is a guest language in this repo, not a tooling language: it is here so that a module can be written in it and run in a browser or a runner, and every such file sits under `services/ws-modules/` beside the manifest that builds and packages it. A `.py` anywhere else is a loose script by another name, carrying every problem one brings -- nothing invokes it, nothing tests it, and nothing fails when it stops working. Short and simple belongs in a task body under `.mise/`; anything more involved gets its own directory under `utilities/` with a `README.md`. - `no-regal-mentions` -- Do not name the Rego linter outside its own config. A policy is written the way Rego reads best; if one of its idioms is deliberate, say what the idiom does rather than which linter asked for it. - `no-roslynator-mentions` -- Do not name the C# analyzer outside the tasks and images that install it. A `#pragma warning disable` already names the diagnostic, which is the part a reader can look up. - `no-ruff-mentions` -- Do not name the Python linter/formatter outside its config and the configs that defer to it. A `# noqa` carries the code it silences and should carry the reason too; the tool that issued the code does not need naming beside it. - `no-ryl-mentions` -- Do not name the YAML linter outside its own config. YAML is written to be read; where its shape is forced, say what forces it, which is almost always the schema the file answers to rather than a linter. - `no-scripts-dir` -- A `scripts/` directory is banned in this repo. Every script belongs in one of exactly two places, and neither of them is a loose tree of files that nothing points at. Short and simple goes inline in a task body under `.mise/`, where it is listed alongside every other entry point and runs by name. Anything more involved gets its own directory under `utilities/`, with a `README.md` saying what it does and how to run it. A file under `scripts/` has neither: no caller, no documentation, and nothing that fails when it stops working. Move it to whichever of the two fits, and drop the directory. - `no-semgrep-mentions` -- Do not name this tool outside its own ruleset and the configs that scope it. A `nosemgrep` marker is the one place the name belongs in source, and it needs the rule id and a reason beside it; anywhere else, pointing at a rule duplicates the message that rule already shows whoever trips it. - `no-shell-scripts` -- A `.sh` file outside `.mise/` is banned. Shell that is short and simple belongs inline in a task body, where it is discoverable and runs by name; shell that has outgrown that is a program, and belongs in its own directory under `utilities/`, written in a language with types, tests and a linter behind it. The one exception is `.mise/`, where a task body too long to sit inside a TOML string is split into a file beside the config that invokes it, named for the interpreter that reads it -- that file is formatted and linted as the shell it is, which a string embedded in a config never can be. - `no-shellcheck-mentions` -- Do not name the shell linter except in the `# shellcheck disable=` pragma, which only works spelled out above the line it covers, and in the configs that run it. The pragma needs the code and the reason the construct is deliberate; nothing else in the script does. - `no-shfmt-mentions` -- Do not name the shell formatter outside the tasks that run it. Formatted shell carries no evidence of who formatted it, so a comment claiming an indent is the formatter's doing cannot be checked. - `no-split-url-in-comment` -- A URL is broken across two comment lines, which leaves a dead link. Rejoin it onto one line, even if that line then runs past the usual width, and re-wrap the surrounding prose around it. A comment reflow that split it cannot be told to keep a URL whole, so the repair is always by hand. - `no-suppressing-unexemptable-rules` (meta) -- This suppression comment exempts the line from a rule the repository holds to every file without exception, either by naming that rule or by naming no rule at all and so silencing every one. Fix the line instead; when a generator wrote it, fix the generator or the task that runs it. A marker that has to stay names the single rule it silences and the reason beside it. - `no-taplo-mentions` -- Do not name the TOML formatter and schema linter outside its config and schemas. Where a generator has to emit TOML the formatter will accept unchanged, say which shape the output must have -- sorted arrays, aligned comments -- rather than naming the tool that would otherwise rewrite it. - `no-test-task-name-mentions` -- Do not name a test or server task outside the jobs and docs that run it. A test file may name the task that supplies something it cannot create for itself -- a browser harness, a listening backend, a port group -- because that is a precondition. Naming one to explain why an assertion exists is not. - `no-todo` -- TODOs are not allowed in this repo. Either resolve the work now, or, if it must defer, add a single-line justification in the surrounding code and add this file's path to no-todo.yaml's paths.exclude allowlist. Do not duplicate a TODO across multiple files -- pick the single canonical home and let `grep TODO` find it there. - `no-trailing-backslash` -- Trailing-backslash line continuations are not allowed in this repo. Keep it on one line, or use a language-appropriate alternative (concat!(...) in Rust, a mise [vars] entry, a YAML/array form). If a file genuinely needs them, add it to this rule's paths.exclude allowlist. - `no-trivy-mentions` -- Do not name the container and manifest scanner outside its ignore file and the task that runs it. Its ignore entries need its own resolution behaviour explained; nothing it scans does. - `no-type-comments` -- Legacy `# type:` comment. Use an inline annotation (`x: T = ...`, `def f(a: T) -> R:`) instead. The compiled Pyrefly type checker that backs `check:python` parses annotations but silently ignores legacy type comments, so a broken one (an undefined name, a stray `--`) rots unseen -- exactly `DeepSource TYP-025`. Annotations are the only form both tools check. `# type: ignore` (the suppression pragma) is exempt. - `no-typos-check-mentions` -- Do not name the spelling checker's tasks outside the config that defines them. An identifier spelled an unusual way is either correct or a typo; if it is correct, say what it means, and add it to the checker's own allowlist rather than explaining it where it appears. - `no-wget` -- Use `curl` instead of `wget` for downloads in this repo. Two download tools means two sets of flags, two retry behaviours and two failure modes for one job, and the difference is easy to get wrong: `wget` writes a file by default while `curl` writes to stdout unless told otherwise, so a command translated between them silently changes what it produces. `curl` is also the one already installed -- it is in the Dockerfiles' COMMON_PACKAGES and present on the CI runners, whereas `wget` is in none of the image package lists, so reaching for it adds an undeclared dependency on whatever the host happens to ship. Note that piping a download into an interpreter stays banned whichever tool fetches it. - `no-windows-shell-scripts` -- A `.bat` or `.ps1` file is banned. Windows is a first-tier platform here, which is precisely why its shell gets no scripts of its own: a task body runs the same way on all five supported platforms, so logic written once is logic every lane exercises, whereas a batch or PowerShell file is reachable from one platform and silently untested on the other four. That is how a Windows-only path rots unnoticed until it is the lane holding up a merge. Write the logic once in a task body under `.mise/`, or in its own directory under `utilities/` when it has outgrown one, and let every platform run the same thing. The `.venv/` exemption covers the activation scripts a virtualenv generates for itself, which are the tool's output rather than anyone's source. - `no-zizmor-mentions` -- Do not name the Actions security auditor except in the `zizmor: ignore[...]` pragma, which only works spelled out, and in the configs that run it. A workflow step is written to be safe; say why it is, and let the pragma carry the audit it waives. - `prefer-yaml-extension` -- Use the .yaml extension, not .yml -- rename the file. - `prefer-yaml-toml-over-json` -- Prefer YAML or TOML over JSON/JSONC/JSONL for config. If the tool requires a JSON-family format, prefer .jsonc over .json so the config can carry inline comments, and add the file to this rule's paths.exclude allowlist. - `published-from-registry` -- A published scenario image may be built only on an image from docker.io or ghcr.io, and has to name that registry. These are the deployments generated against released artifacts, so every base they build on is something the operator pulls on trust; holding each `FROM` to the two registries this project publishes to and draws its upstream images from, spelled out rather than left to the daemon's default, keeps an image from anywhere else -- or a build context standing in for one -- from becoming part of a deployment unseen. A flag such as `--platform` is refused the same way, since it is not a registry. - `published-from-stage-only` -- In a published scenario image, `--from=` may name only one of the Dockerfile's own stages, `released` or `deps`. `COPY --from=` and `RUN --mount=...,from=` both take an image reference as readily as a stage name, and a bare name that matches no stage is pulled from a registry -- so either is a way to bring an image into the build that no `FROM` line declares and the rule holding every `FROM` to docker.io and ghcr.io never sees. The stage names are the ones et-cli generates. - `published-no-additional-contexts` -- A published deployment hands its image build no additional context, neither a compose `additional_contexts:` nor a `docker build --build-context`. A named context substitutes whatever it points at -- a local image, a directory, another service's build -- for the name a `FROM` or `COPY --from=` uses, so it is a way round the rules holding a published Dockerfile to registry images it declares itself. A published scenario image names the released hub directly and fetches its modules from their releases, so it has nothing to be handed. - `published-no-relative-path` -- A published deployment names no relative path, so neither `..` nor `./` has a place in one. These are the deployments generated against released artifacts, for someone who has the releases and not this repository. A path climbing out of the deployment directory is a path into a checkout they do not have -- a compose build context of `../../../..` reads this repository's tree, which only works by accident from inside it -- and a `./` path is relative to wherever a command happens to run rather than to anything the deployment fixes. Fetch what the deployment needs from its releases, and let each tool's own default stand for "this directory", so the deployment builds and runs from its directory alone. - `python-silent-broad-except` -- Silently discarding every ordinary exception can conceal programming errors and lost work. Catch the expected exception types and explicitly report, recover from, or propagate the failure. Check-only: a safe recovery policy cannot be inferred from the handler alone. - `rego-rule-summary` (meta) -- This Rego rule has no summary comment. The generated catalogue of every check lists each policy rule by the first line of the comment above it, and refuses to build without one, so describe what the rule enforces in a comment opening at column 0 between the previous rule's closing brace and this rule. The file's header comment does not count: it sits above the `package` line and describes the policy, not any one rule in it. - `single-key-env-subtable-should-be-inline` -- `[tasks.X.env]` subtable has a single `KEY = "value"` assignment. Inline it on the parent task as `env = { KEY = "value" }` when the resulting line fits the editorconfig line length -- subtable form is reserved for multi-key envs. - `single-line-description-in-mise-config` -- mise task `description` fields must be single-line `"..."` strings, not multi-line `"""..."""`. Keep the description short enough to fit on one line. - `sorted-lists` -- Keep this list in ASCII order of each item as written: the traits in a `#[derive(...)]` (`Clone, Debug, Deserialize, Eq, PartialEq, Serialize`, a qualified `strum::EnumIter` after every bare name), the lints in an `#[expect(...)]` before its `reason` (`clippy::` lints together, rustc lints after them), and adjacent `feature = "..."` predicates in an `any(...)` / `all(...)`. A sorted list reads the same everywhere, makes a missing or duplicated entry easy to spot, and keeps a diff to the one entry that changed. - `task-shell-must-use-task-shell-var` -- Declare a task `shell` through one of the `task_shell` vars, never as a bare `bash ...` command line. The vars run the body through a wrapper script instead of `-c`, which is what repairs the mangled PATH mise hands a Windows task; a raw `bash ... -c` bypasses the wrapper and every tool goes missing on the Windows lanes. Use `{{ vars.task_shell }}`, or `task_shell_trace` for xtrace, or `task_shell_plain` for a body that checks its own exit codes. ## `shellcheck` - `shellcheck-check` (`default` env; run by `check:rust`) -- shellcheck the standalone shell fragments committed under .mise/. - `shellcheck-mise-check` (`default` env; run by `check:rust`) -- shellcheck every bash-shell mise task body across .mise/config*.toml. Config: `.mise/shellcheck-mise.jq`. ## `taplo` - `taplo-check` (`default` env; run by `check:rust`). Config: `config/taplo.toml`, `config/taplo/mise-cargo-backend-allowlist.schema.json`, `config/taplo/mise-no-ubi-backend.schema.json`, `config/taplo/mise-run-not-array.schema.json`, `config/taplo/no-banned-deps.schema.json`, `config/taplo/no-lib-name.schema.json`, `config/taplo/no-path-deps.schema.json`, `config/taplo/no-wildcard-or-git-deps.schema.json`, `config/taplo/require-lib-doctest-false.schema.json`, `config/taplo/require-lints-section.schema.json`, `config/taplo/require-workspace-deps.schema.json`. - `taplo-fmt-check` (`default` env; run by `check:rust`). Repaired by `taplo-fmt`. Config: `config/taplo.toml`. Rules, from `config/taplo`: - `mise-cargo-backend-allowlist` -- mise [tools] -- cargo backend allowlist: `cargo:` source build; tools with no prebuilt + os-scoped fallbacks (findutils, ryl). - `mise-no-ubi-backend` -- mise [tools] -- no ubi backend: The `ubi:` backend is deprecated; use `http:` instead. - `mise-run-not-array` -- mise tasks -- `run` must be a string, not an array: `run` must be a string, not an array: taplo reorder_arrays would re-sort the commands. - `no-banned-deps` -- Cargo.toml -- banned deps: Crates this repo deliberately doesn't take on; const = rejection message. - `no-lib-name` -- Workspace member Cargo.toml -- [lib] must not override the crate name: The library name must default from the package name; an explicit `[lib] name` rename is banned. - `no-path-deps` -- Workspace member Cargo.toml -- no path dependencies: Forbids `path = ...` deps outside workspace root; `const` is a hack so taplo prints it as the error. - `no-wildcard-or-git-deps` -- Workspace member Cargo.toml -- no wildcard or git deps: Forbids `*` and inline `git = ...` in [dependencies] / [dev-dependencies] / [build-dependencies]. - `require-lib-doctest-false` -- Workspace member Cargo.toml -- [lib] must set doctest = false: Pairs with the ban on fenced blocks in doc comments; runnable examples go in tests/. - `require-lints-section` -- Workspace member Cargo.toml -- must inherit workspace lints: Member crates must include `[lints] workspace = true` to inherit workspace lints. - `require-workspace-deps` -- Workspace member Cargo.toml -- deps must inherit: [dependencies] / [dev-dependencies] / [build-dependencies] entries must use `.workspace = true`. ## `trivy` - `trivy-check` (`default` env; run by `check:rust`) -- Check every Dockerfile and generated Kubernetes manifest for hardening findings. Config: `config/trivyignore.yaml`. ## `typos` - `typos-check` (`default` env; run by `check:rust`). Repaired by `typos-fix`. Config: `config/typos.toml`. ## `xcrun` - `macos-sdk-check` (`default` env; not run by `mise run check`) -- Fail if the macOS SDK is missing or older than the minimum this repo builds against. ## `zig` - `zig-check` (`zig` env; run by `check:zig`). Repaired by `zig-fmt`. ## `zizmor` - `zizmor-check` (`default` env; run by `check:rust`) -- Audit GitHub Actions workflows + composite actions for security issues (zizmor). Config: `config/zizmor.yaml`. ## Shell-script checks - `action-validator-check` (`windows` env; run by `check:rust`). - `agent-lint-check` (`windows` env; run by `check:rust`). - `branch-up-to-date-check` (`default` env; run by `check:rust`) -- Fail if the current branch does not already contain origin/main (rebase needed). - `check-ghcr-visibility` (`maint` env; not run by `mise run check`) -- Verify every GHCR package is public; print settings URLs for any still private. - `code-scanning-check` (`default` env; not run by `mise run check`) -- Fail if the GitHub repository has any open code-scanning alert. - `dockerignore-check` (`default` env; run by `check:rust`) -- Fail if .dockerignore is stale vs .gitignore (run `mise run gen:dockerignore`). - `gen-specs-check` (`default` env; run by `check:rust`) -- Fail if any checked-in et-int-gen artifact is stale. - `mise-lock-check` (`default` env; run by `check:rust`) -- Fail if .mise/mise*.lock or .mise/locks/ differ from what a full install resolves. - `readme-mise-version-check` (`default` env; run by `check:rust`) -- Fail if README.md's mise version differs from min_version in .mise/config.toml. - `verification-check` (`default` env; run by `check:rust`) -- Fail if generated scenario output files are stale.