Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
connorads avatar

Mechanical Enforcement

  • 16 installs
  • 15 repo stars
  • Updated August 1, 2026
  • connorads/dotfiles

Catalogues linter rules, TypeScript flags, and boundary checks to make bug classes and design drift mechanically impossible when hardening a project.

About

Provides a curated catalogue of linter rules, TypeScript flags, clippy thresholds, and architectural boundary checks to make bug classes and design drift mechanically impossible. A developer uses it when setting up or hardening linting on a project, pairing with the hk skill for hook wiring.

  • Curated catalogue of linter rules, TypeScript flags, clippy thresholds, and boundary checks
  • Principle: mechanical over social, with types first, lint second, tests third

Mechanical Enforcement by the numbers

  • 16 all-time installs (skills.sh)
  • Ranked #769 of 1,354 Code Review & Quality skills by installs in the Skillselion catalog
  • Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/connorads/dotfiles --skill mechanical-enforcement

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs16
repo stars15
Last updatedAugust 1, 2026
Repositoryconnorads/dotfiles

What it does

Catalogues linter rules, TypeScript flags, and boundary checks to make bug classes and design drift mechanically impossible when hardening a project.

Files

SKILL.mdMarkdownGitHub ↗

Mechanical Enforcement

Rules a reviewer would otherwise have to remember belong in a linter. This skill is the curated catalogue of rules, the linters that enforce them, and the rationale for each — so a new project can be hardened without re-deriving the set.

This is a content skill, not a tool. It provides rules and snippets. For wiring those rules into git hooks, see the hk skill.

Principles

1. Mechanical over social. If a rule relies on a reviewer remembering it, it will drift. Encode it in a linter, a type, or a test — never in a convention. 2. Types first, lint second, tests third. Prefer strict TypeScript / Pydantic / clippy to a custom lint rule. Reach for a lint rule when the type system can't express it. Reach for a test only when neither can. 3. Architectural boundaries are linter rules. Layers (domain ← infra, utilities ← server, UI ← schemas) are enforced with no-restricted-imports / no-restricted-syntax, not trusted to vigilance. 4. Auto-fix where possible, gate where not. Formatters and whitespace fixers run with fix = true and re-stage. Correctness rules gate the commit. 5. Prefer opinionated presets, override minimally. Ultracite for Biome, @commitlint/config-conventional for commits, next/core-web-vitals for Next. Only override with a comment explaining why. 6. *The why lives with the rule*. Every non-obvious override has an inline comment saying what would break if it were removed.

When to use this skill

  • Setting up linting in a new project → pick linters from the table below, copy snippets from references/, wire with the hk skill.
  • Hardening an existing project → audit against the rules catalogue, add the missing ones.
  • A bug just happened → ask "what rule would have caught this mechanically?" and add it here.
  • Choosing a linter for an unfamiliar stack → see the picks table.

Linter picks by stack

Use the tool in the Primary column first; reach for the Also column only when the primary can't express the rule.

StackFormatterPrimary linterAlsoType-checkNotes
TypeScript / React / NextBiome (via Ultracite presets core, react, next)BiomeESLint flat config — only for no-restricted-imports, no-restricted-syntax, jsx-a11y, framework plugins (next, storybook)tsc --noEmit strictUltracite is the default for new projects. Raw Biome only if Ultracite doesn't support the framework.
TypeScript (library / node)BiomeBiometsc --noEmit strictSkip ESLint entirely unless you need boundary rules.
Pythonruff formatruffbasedpyright strict (or pyright)ruff replaces black + isort + flake8 + pylint.
Rustrustfmtclippy (-D warnings)cargo-denycargo checkclippy::pedantic selectively; full pedantic is too noisy. See Rust sections below for thresholds and common allows.
Gogofmt / gofumptgolangci-lintgo vetEnable errcheck, govet, staticcheck, revive.
Shellshfmtshellcheck-e SC2086 only with comment.
MarkdownrumdlrumdlHandles frontmatter too.
Nixnixfmtdeadnix + statix
YAMLyamllint
Commit messagescommitlint (@commitlint/config-conventional)One-line config. See references/commitlint.config.js.
SecretsgitleaksAlways add — cheap, high-signal.
TypostyposFast, auto-fixes, tiny false-positive rate.
GitHub Actions / CIzizmorSecurity audit of .github/workflows/*.yml + action.yml. SARIF + --format=github annotations. Complements gitleaks, not overlapping.

Rules catalogue

Rules are organised by concern, not by linter. Each entry gives: what it prevents, how to encode it, and known exceptions.

Type safety

RuleEncode withPreventsNotes
Full strict modetsconfig.json: "strict": trueMost null/undefined footgunsNon-negotiable.
Indexed access returns `T \undefined`"noUncheckedIndexedAccess": truearr[0].foo crashing on empty arrays
Dead code fails build"noUnusedLocals": true, "noUnusedParameters": trueDrifted imports, zombie variablesPrefix with _ to intentionally keep an unused param.
Only erasable TS syntax"erasableSyntaxOnly": true (TS 5.8+)enum, namespace, constructor param props — things that don't survive pure type-strippingEnables deno/bun/swc/esbuild interop without a TS runtime. Breaks existing code using enum; migrate to as const unions.
No anyBiome noExplicitAny (error)Escape hatch from the type systemUse unknown + narrowing.
No as Type assertionsESLint @typescript-eslint/consistent-type-assertions with assertionStyle: "never"Silent lies to the compilerAllowed exceptions (document each with eslint-disable-next-line + reason): as const, DOM APIs after null checks, untyped-library interop, intentionally-invalid test fixtures.
No ! non-null assertionESLint @typescript-eslint/no-non-null-assertionSilent runtime crashesUse a proper null check or throw a narrowed error.
Prefer import typeBiome useImportTypeAccidental runtime imports of type-only modulesAuto-fixable.

Error handling

RuleEncode withPreventsNotes
No bare catch / swallowed errorsBiome noCatchAssign, useErrorMessage; ESLint no-empty with allowEmptyCatch: falseErrors disappearing into the voidNarrow in the catch (catch (e) { if (e instanceof FooError) ... }) or rethrow.
No catch-all re-throw without causeCustom no-restricted-syntax catching rethrows without { cause }Losing error contextRequired pattern: throw new Error("while doing X", { cause: e }).
Prefer Result types at domain boundariesConvention + review; no linterException-driven control flow in pure codeExceptions live at the imperative shell only.
No console.* in prod codeBiome noConsole with allow: ["warn", "error"]Logs leaking to user consolesUse the project's logger.

Architectural boundaries

Use no-restricted-imports and no-restricted-syntax to make illegal graphs uncompilable. The catalogue of patterns:

  • Pure layer cannot import side-effectful layer. files: ["src/utilities/**"] + no-restricted-imports banning next/cache, next/headers, next/navigation, ORM runtime modules. Use allowTypeImports: true for types you still want visible. Exempt one or two intentionally coupled files (queries.ts, revalidate.ts) via ignores.
  • UI cannot import schemas directly. files: ["src/components/**"] + no-restricted-imports patterns banning @/collections/* (or whichever path holds your DB schemas). UI should depend on generated types, not schema source — otherwise a UI tweak forces a migration.
  • Raw SQL only in the query layer. no-restricted-syntax on TaggedTemplateExpression[tag.name='sql'] everywhere except src/db/**. Also ban raw driver imports (ImportDeclaration[source.value='postgres']) outside the same directory.
  • Dynamic `import()` only via named wrappers. no-restricted-syntax on ImportExpression outside next/dynamic / React.lazy. Prevents ad-hoc chunking that defeats SSR.

Full working snippets live in references/eslint-boundaries.mjs.

UI hygiene (React / Next)

RuleEncode withPreventsNotes
No raw <input> / <button> / <a> outside the component libraryno-restricted-syntax on JSXOpeningElement[name.name='input'] (etc.) in app/feature codeDrift from the design systemExempt the UI library path (src/components/ui/**). Error message points at the wrapper component.
jsx-a11y/recommended onESLint plugin:jsx-a11y/recommended via flat configAccessibility regressionsTurn off no-noninteractive-tabindex — the axe-mandated scrollable-region-focusable pattern conflicts.
No inline stylesBiome noInlineStyles (or ESLint react/forbid-dom-props)Design-system bypassAllow style on one or two charting components with a disable comment.
useTopLevelRegex (Biome)default in UltraciteRegex recompiled on every call; inline regex in test assertionsPrefer .toThrow("Cannot submit:") over .toThrow(/Cannot submit:/).

Import hygiene

RuleEncode withPrevents
Sorted + grouped importsBiome organizeImports on formatMerge conflicts; inconsistency
No cyclesmadge (madge --circular) in pre-commit or eslint-plugin-import's no-cycleModule init-order bugs
No default exports (optional)Biome noDefaultExport / ESLint import/no-default-exportInconsistent naming at import sites; poor rename refactoring. Exempt Next.js pages/layouts where defaults are required.
Unique function namesno-restricted-syntax on duplicate FunctionDeclaration identifiers across a file; fallback is a grep-based hk stepDuplicate helpers being written instead of discovered. Grep check catches the cross-file case ESLint can't.

Testing

RuleEncode withPrevents
No .only committedBiome noFocusedTests (Ultracite default); or ESLint vitest/no-focused-testsAccidentally skipping the rest of the suite in CI
No inline regex in assertionsBiome useTopLevelRegexFlaky matches and poor error messages
Coverage threshold enforced pre-commithk step running vitest run --coverage + vitest config thresholds: { 100: true }Untested branches slipping in. Use /* v8 ignore next */ for unreachable defensive code.
No mocks in unit testsConvention + reviewTests that pass but mask integration bugs

Secrets & supply chain

RuleEncode withPrevents
No committed secretsgitleaks pre-commit stepToken leaks
Pinned dependencies with quarantinepnpm minimum-release-age, npm min-release-age, uv exclude-newer, mise install_beforeCompromised releases
No --no-verifyDocumented in project CLAUDE.md / AGENTS.md; not technically preventableBypassing the whole gate. Cultural rule — reinforce in every project's agent docs.
Pinned + safe GitHub Actions workflowszizmor (gate on exit ≥ 11)Unpinned actions (unpinned-uses), dangerous triggers (dangerous-triggerspull_request_target/workflow_run), template injection into run: (template-injection), over-broad permissions: (excessive-permissions), impostor commits, typosquatted actions

Rust: type safety & correctness

RuleEncode withPreventsNotes
Deny all default warningsclippy -D warningsWarnings accumulating silentlyNon-negotiable baseline.
Pedantic lints (selective)[workspace.lints.clippy] pedantic = { level = "warn", priority = -1 }Broader code quality issuesStart at warn, promote to deny once clean. Allow noisy lints per-project — see common allows table below.
Unused resultsclippy let_underscore_must_use, unused_resultsSilently discarding important return valuesComplements #[must_use] annotations.
Unsafe visibility[workspace.lints.rust] unsafe_code = "warn"Unsafe blocks spreading unnoticedwarn not deny — FFI crates need escape hatch with per-crate override.

Rust: complexity thresholds (clippy.toml)

All settings go in clippy.toml at the workspace root. See references/clippy-thresholds.toml for a drop-in file.

SettingDefaultRecommendedPrevents
too-many-lines-threshold100100Functions too long to review in one screen. Per-fn #[allow(clippy::too_many_lines)] for faithful translations (e.g. ASM ports).
too-many-arguments-threshold77God-functions with too many inputs.
cognitive-complexity-threshold2525Deeply nested/branching logic.
type-complexity-threshold250250Deeply nested generics.
max-fn-params-bools33Boolean-parameter blindness.
max-struct-bools33Structs that should use enums instead.
disallowed-names["foo","baz","quux"]["foo","bar","baz","quux"]Placeholder names leaking into prod.

Rust: common pedantic allows

When enabling clippy::pedantic, these lints are typically too noisy. Allow them at workspace level and document why so projects don't re-derive the set. See references/rust-workspace-lints.toml for a drop-in config.

LintWhen to allowWhy
cast-possible-truncationNumeric/embedded/emulator codeIntentional width casts are the norm
cast-possible-losslessSameWould flag every u8 as u16
cast-precision-lossFloat/audio/timing codef64 as f32 is intentional
cast-sign-lossBitwise/register codei32 as u32 is intentional
module-name-repetitionsAlwaysIdiomatic Rust (error::Error)
must-use-candidateAlwaysToo many suggestions, low signal
missing-errors-docNon-library cratesOnly useful for published APIs
missing-panics-docNon-library cratesSame
similar-namesDomain code with similar identifiersRegister names, coordinate pairs
unreadable-literalCode with hex addresses/constants0x3CD70 shouldn't need 0x0003_CD70
wildcard-importsTest modules, enum re-exportsCommon Rust pattern
struct-excessive-boolsState/config structsGame state, feature flags

Rust: workspace lint wiring

Requires Rust 1.74+. Define lints once in root Cargo.toml, inherit in each crate. FFI/sys crates get per-crate overrides. See references/rust-workspace-lints.toml for a complete template.

# Root Cargo.toml
[workspace.lints.clippy]
pedantic = { level = "warn", priority = -1 }
# ... project-specific allows ...

[workspace.lints.rust]
unsafe_code = "warn"

# Each crate's Cargo.toml
[lints]
workspace = true

# FFI crate override example
[lints.clippy]
missing-safety-doc = "allow"

Rust: supply chain (cargo-deny)

cargo-deny enforces dependency policy. See references/cargo-deny.toml for a template deny.toml.

ConcernConfig sectionWhat it catchesNotes
Known vulnerabilities[advisories]CVEs in transitive deps via RustSec DBSet severity = "low" to flag everything.
Licence compliance[licenses] with allowlistUnapproved or missing SPDX licencesUse [[licenses.clarify]] for deps with missing metadata.
Banned crates[bans]Specific crates (e.g. openssl → use rustls) or duplicate versionsmultiple-versions = "warn" catches dep tree bloat.
Registry restriction[sources]Deps from unknown registries or git reposunknown-registry = "deny", unknown-git = "warn".

Commit messages

// commitlint.config.js
export default { extends: ["@commitlint/config-conventional"] };

Wire via hk's commit-msg hook (see references/hk-steps.pkl). Nothing else to configure.

Composition with the hk skill

This skill gives you what to enforce. The hk skill gives you how to wire it.

The typical mapping (TypeScript):

tier 1 (format/fix)     → trailing-whitespace, newlines, typos, rumdl, biome fix
tier 2 (lint/gate)      → biome check, eslint, gitleaks, yamllint, check-merge-conflict, zizmor --offline (glob: .github/workflows/*.{yml,yaml} + action.yml)
tier 3 (typecheck)      → tsc --noEmit (or tsgo)
tier 4 (test)           → vitest run --coverage
commit-msg              → commitlint

The typical mapping (Rust):

tier 1 (format/fix)     → trailing-whitespace, newlines, typos, cargo-fmt
tier 2 (lint/gate)      → cargo-clippy -D warnings, gitleaks, cargo-deny
tier 3 (typecheck)      → cargo check (usually redundant with clippy but catches cfg issues)
tier 4 (test)           → cargo test (scoped to changed crates via glob)

Use fix = true + stash = "git" on pre-commit so tier 1 auto-fixes and re-stages. See references/hk-steps.pkl for a full worked example.

Adding a new rule

When a bug escapes to review or production, the retro question is: what rule would have caught this mechanically?

1. Identify the smallest AST pattern, import, or type flag that expresses the rule. 2. Pick the linter that already owns that concern (see picks table). 3. Add it, with an inline comment explaining the failure mode it prevents. 4. Add an entry to the relevant rules-catalogue section above (in this SKILL.md) with the same rationale. 5. If it's a new type of rule worth sharing, add a snippet to references/.

References

TypeScript / JS

  • references/typescript-strict.jsonc — strict compilerOptions block (drop-in)
  • references/biome-ultracite.jsonc — Biome config extending Ultracite with override pattern
  • references/eslint-boundaries.mjs — layered no-restricted-imports + no-restricted-syntax examples
  • references/commitlint.config.js — one-line conventional-commits config

Rust

  • references/clippy-thresholds.tomlclippy.toml with recommended complexity thresholds (drop-in)
  • references/rust-workspace-lints.toml[workspace.lints] block with pedantic + common allows (drop-in)
  • references/cargo-deny.tomldeny.toml template for licence/advisory/ban enforcement (drop-in)

Cross-stack

  • references/hk-steps.pkl — worked hk.pkl step graph
  • Ultracite — Biome preset bundle
  • hk — git hook manager

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.