
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-enforcementAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 16 |
|---|---|
| repo stars | ★ 15 |
| Last updated | August 1, 2026 |
| Repository | connorads/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
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 thehkskill. - 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.
| Stack | Formatter | Primary linter | Also | Type-check | Notes |
|---|---|---|---|---|---|
| TypeScript / React / Next | Biome (via Ultracite presets core, react, next) | Biome | ESLint flat config — only for no-restricted-imports, no-restricted-syntax, jsx-a11y, framework plugins (next, storybook) | tsc --noEmit strict | Ultracite is the default for new projects. Raw Biome only if Ultracite doesn't support the framework. |
| TypeScript (library / node) | Biome | Biome | — | tsc --noEmit strict | Skip ESLint entirely unless you need boundary rules. |
| Python | ruff format | ruff | — | basedpyright strict (or pyright) | ruff replaces black + isort + flake8 + pylint. |
| Rust | rustfmt | clippy (-D warnings) | cargo-deny | cargo check | clippy::pedantic selectively; full pedantic is too noisy. See Rust sections below for thresholds and common allows. |
| Go | gofmt / gofumpt | golangci-lint | — | go vet | Enable errcheck, govet, staticcheck, revive. |
| Shell | shfmt | shellcheck | — | — | -e SC2086 only with comment. |
| Markdown | rumdl | rumdl | — | — | Handles frontmatter too. |
| Nix | nixfmt | deadnix + statix | — | — | |
| YAML | — | yamllint | — | — | |
| Commit messages | — | commitlint (@commitlint/config-conventional) | — | — | One-line config. See references/commitlint.config.js. |
| Secrets | — | gitleaks | — | — | Always add — cheap, high-signal. |
| Typos | — | typos | — | — | Fast, auto-fixes, tiny false-positive rate. |
| GitHub Actions / CI | — | zizmor | — | — | Security 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
| Rule | Encode with | Prevents | Notes |
|---|---|---|---|
| Full strict mode | tsconfig.json: "strict": true | Most null/undefined footguns | Non-negotiable. |
| Indexed access returns `T \ | undefined` | "noUncheckedIndexedAccess": true | arr[0].foo crashing on empty arrays |
| Dead code fails build | "noUnusedLocals": true, "noUnusedParameters": true | Drifted imports, zombie variables | Prefix 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-stripping | Enables deno/bun/swc/esbuild interop without a TS runtime. Breaks existing code using enum; migrate to as const unions. |
No any | Biome noExplicitAny (error) | Escape hatch from the type system | Use unknown + narrowing. |
No as Type assertions | ESLint @typescript-eslint/consistent-type-assertions with assertionStyle: "never" | Silent lies to the compiler | Allowed 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 assertion | ESLint @typescript-eslint/no-non-null-assertion | Silent runtime crashes | Use a proper null check or throw a narrowed error. |
Prefer import type | Biome useImportType | Accidental runtime imports of type-only modules | Auto-fixable. |
Error handling
| Rule | Encode with | Prevents | Notes |
|---|---|---|---|
No bare catch / swallowed errors | Biome noCatchAssign, useErrorMessage; ESLint no-empty with allowEmptyCatch: false | Errors disappearing into the void | Narrow in the catch (catch (e) { if (e instanceof FooError) ... }) or rethrow. |
| No catch-all re-throw without cause | Custom no-restricted-syntax catching rethrows without { cause } | Losing error context | Required pattern: throw new Error("while doing X", { cause: e }). |
| Prefer Result types at domain boundaries | Convention + review; no linter | Exception-driven control flow in pure code | Exceptions live at the imperative shell only. |
No console.* in prod code | Biome noConsole with allow: ["warn", "error"] | Logs leaking to user consoles | Use 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-importsbanningnext/cache,next/headers,next/navigation, ORM runtime modules. UseallowTypeImports: truefor types you still want visible. Exempt one or two intentionally coupled files (queries.ts,revalidate.ts) viaignores. - UI cannot import schemas directly.
files: ["src/components/**"]+no-restricted-imports patternsbanning@/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-syntaxonTaggedTemplateExpression[tag.name='sql']everywhere exceptsrc/db/**. Also ban raw driver imports (ImportDeclaration[source.value='postgres']) outside the same directory. - Dynamic `import()` only via named wrappers.
no-restricted-syntaxonImportExpressionoutsidenext/dynamic/React.lazy. Prevents ad-hoc chunking that defeats SSR.
Full working snippets live in references/eslint-boundaries.mjs.
UI hygiene (React / Next)
| Rule | Encode with | Prevents | Notes |
|---|---|---|---|
No raw <input> / <button> / <a> outside the component library | no-restricted-syntax on JSXOpeningElement[name.name='input'] (etc.) in app/feature code | Drift from the design system | Exempt the UI library path (src/components/ui/**). Error message points at the wrapper component. |
jsx-a11y/recommended on | ESLint plugin:jsx-a11y/recommended via flat config | Accessibility regressions | Turn off no-noninteractive-tabindex — the axe-mandated scrollable-region-focusable pattern conflicts. |
| No inline styles | Biome noInlineStyles (or ESLint react/forbid-dom-props) | Design-system bypass | Allow style on one or two charting components with a disable comment. |
useTopLevelRegex (Biome) | default in Ultracite | Regex recompiled on every call; inline regex in test assertions | Prefer .toThrow("Cannot submit:") over .toThrow(/Cannot submit:/). |
Import hygiene
| Rule | Encode with | Prevents |
|---|---|---|
| Sorted + grouped imports | Biome organizeImports on format | Merge conflicts; inconsistency |
| No cycles | madge (madge --circular) in pre-commit or eslint-plugin-import's no-cycle | Module init-order bugs |
| No default exports (optional) | Biome noDefaultExport / ESLint import/no-default-export | Inconsistent naming at import sites; poor rename refactoring. Exempt Next.js pages/layouts where defaults are required. |
| Unique function names | no-restricted-syntax on duplicate FunctionDeclaration identifiers across a file; fallback is a grep-based hk step | Duplicate helpers being written instead of discovered. Grep check catches the cross-file case ESLint can't. |
Testing
| Rule | Encode with | Prevents |
|---|---|---|
No .only committed | Biome noFocusedTests (Ultracite default); or ESLint vitest/no-focused-tests | Accidentally skipping the rest of the suite in CI |
| No inline regex in assertions | Biome useTopLevelRegex | Flaky matches and poor error messages |
| Coverage threshold enforced pre-commit | hk 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 tests | Convention + review | Tests that pass but mask integration bugs |
Secrets & supply chain
| Rule | Encode with | Prevents |
|---|---|---|
| No committed secrets | gitleaks pre-commit step | Token leaks |
| Pinned dependencies with quarantine | pnpm minimum-release-age, npm min-release-age, uv exclude-newer, mise install_before | Compromised releases |
No --no-verify | Documented in project CLAUDE.md / AGENTS.md; not technically preventable | Bypassing the whole gate. Cultural rule — reinforce in every project's agent docs. |
| Pinned + safe GitHub Actions workflows | zizmor (gate on exit ≥ 11) | Unpinned actions (unpinned-uses), dangerous triggers (dangerous-triggers — pull_request_target/workflow_run), template injection into run: (template-injection), over-broad permissions: (excessive-permissions), impostor commits, typosquatted actions |
Rust: type safety & correctness
| Rule | Encode with | Prevents | Notes |
|---|---|---|---|
| Deny all default warnings | clippy -D warnings | Warnings accumulating silently | Non-negotiable baseline. |
| Pedantic lints (selective) | [workspace.lints.clippy] pedantic = { level = "warn", priority = -1 } | Broader code quality issues | Start at warn, promote to deny once clean. Allow noisy lints per-project — see common allows table below. |
| Unused results | clippy let_underscore_must_use, unused_results | Silently discarding important return values | Complements #[must_use] annotations. |
| Unsafe visibility | [workspace.lints.rust] unsafe_code = "warn" | Unsafe blocks spreading unnoticed | warn 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.
| Setting | Default | Recommended | Prevents |
|---|---|---|---|
too-many-lines-threshold | 100 | 100 | Functions too long to review in one screen. Per-fn #[allow(clippy::too_many_lines)] for faithful translations (e.g. ASM ports). |
too-many-arguments-threshold | 7 | 7 | God-functions with too many inputs. |
cognitive-complexity-threshold | 25 | 25 | Deeply nested/branching logic. |
type-complexity-threshold | 250 | 250 | Deeply nested generics. |
max-fn-params-bools | 3 | 3 | Boolean-parameter blindness. |
max-struct-bools | 3 | 3 | Structs 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.
| Lint | When to allow | Why |
|---|---|---|
cast-possible-truncation | Numeric/embedded/emulator code | Intentional width casts are the norm |
cast-possible-lossless | Same | Would flag every u8 as u16 |
cast-precision-loss | Float/audio/timing code | f64 as f32 is intentional |
cast-sign-loss | Bitwise/register code | i32 as u32 is intentional |
module-name-repetitions | Always | Idiomatic Rust (error::Error) |
must-use-candidate | Always | Too many suggestions, low signal |
missing-errors-doc | Non-library crates | Only useful for published APIs |
missing-panics-doc | Non-library crates | Same |
similar-names | Domain code with similar identifiers | Register names, coordinate pairs |
unreadable-literal | Code with hex addresses/constants | 0x3CD70 shouldn't need 0x0003_CD70 |
wildcard-imports | Test modules, enum re-exports | Common Rust pattern |
struct-excessive-bools | State/config structs | Game 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.
| Concern | Config section | What it catches | Notes |
|---|---|---|---|
| Known vulnerabilities | [advisories] | CVEs in transitive deps via RustSec DB | Set severity = "low" to flag everything. |
| Licence compliance | [licenses] with allowlist | Unapproved or missing SPDX licences | Use [[licenses.clarify]] for deps with missing metadata. |
| Banned crates | [bans] | Specific crates (e.g. openssl → use rustls) or duplicate versions | multiple-versions = "warn" catches dep tree bloat. |
| Registry restriction | [sources] | Deps from unknown registries or git repos | unknown-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 → commitlintThe 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— strictcompilerOptionsblock (drop-in)references/biome-ultracite.jsonc— Biome config extending Ultracite with override patternreferences/eslint-boundaries.mjs— layeredno-restricted-imports+no-restricted-syntaxexamplesreferences/commitlint.config.js— one-line conventional-commits config
Rust
references/clippy-thresholds.toml—clippy.tomlwith recommended complexity thresholds (drop-in)references/rust-workspace-lints.toml—[workspace.lints]block with pedantic + common allows (drop-in)references/cargo-deny.toml—deny.tomltemplate for licence/advisory/ban enforcement (drop-in)
Cross-stack
// Biome config extending Ultracite presets.
// Ultracite bundles opinionated Biome rules for core/react/next — use it
// instead of hand-rolling a Biome config. Override only with a comment
// explaining *what would break* if the override were removed.
{
"$schema": "https://biomejs.dev/schemas/2.4.9/schema.json",
"extends": [
"ultracite/biome/core",
"ultracite/biome/react",
"ultracite/biome/next"
],
"files": {
"includes": [
"!.next/",
"!.open-next/",
"!node_modules/",
"!**/*.generated.*",
"!pnpm-lock.yaml",
"!.wrangler/",
"!storybook-static/"
]
},
"linter": {
"rules": {
"correctness": {
// Tailwind's `theme()` function in CSS is legal but Biome doesn't know.
"noUnknownFunction": {
"level": "error",
"options": { "ignore": ["theme"] }
}
},
"a11y": {
// tabIndex={0} on scrollable regions is the axe-mandated pattern
// (scrollable-region-focusable). Conflicts with this rule; keep it off.
"noNoninteractiveTabindex": "off"
},
"style": {
// Allow filenames that don't match strict kebab-case — Next.js app router
// uses `[slug]`, `(group)` etc. that fail camelCase convention checks.
"useFilenamingConvention": "off",
// Barrel re-exports are a legitimate pattern.
"noExportedImports": "off"
},
"security": {
// JSON-LD structured data requires static dangerouslySetInnerHTML blocks.
// Keep the override narrow — review any new use sites by hand.
"noDangerouslySetInnerHtml": "off"
}
}
}
}
# deny.toml — cargo-deny template
# Copy to project root as `deny.toml`
# Docs: https://embarkstudios.github.io/cargo-deny/
[graph]
all-features = true
# --- Security advisories (RustSec DB) ---
[advisories]
severity = "low" # flag everything, not just critical
# --- Licence compliance ---
[licenses]
unlicensed = "deny"
confidence-threshold = 0.8
copyleft = "deny"
allow = [
"MIT",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
"ISC",
"Unicode-3.0",
"Unicode-DFS-2016",
"Zlib",
]
# For deps with missing/ambiguous licence metadata:
# [[licenses.clarify]]
# crate = "some-crate"
# expression = "MIT"
# license-files = [{ path = "LICENSE", hash = 0xDEADBEEF }]
# --- Crate bans ---
[bans]
multiple-versions = "warn" # flag duplicate versions in the dep tree
# Ban specific crates (uncomment/extend as needed):
# deny = [
# { crate = "openssl", use-instead = "rustls" },
# ]
# --- Source restrictions ---
[sources]
unknown-registry = "deny"
unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# allow-git = ["https://github.com/org/repo"]
# clippy.toml — drop-in complexity thresholds
# Copy to project root as `clippy.toml`
# Docs: https://doc.rust-lang.org/clippy/lint_configuration.html
# Function complexity
too-many-lines-threshold = 100 # pedantic: too_many_lines
too-many-arguments-threshold = 7 # too_many_arguments
cognitive-complexity-threshold = 25 # cognitive_complexity
type-complexity-threshold = 250 # type_complexity_limit
# Struct/param quality
max-fn-params-bools = 3 # fn_params_excessive_bools
max-struct-bools = 3 # struct_excessive_bools
# Naming
disallowed-names = ["foo", "bar", "baz", "quux"] # disallowed_names
// Conventional commits — nothing to configure.
// Wire via hk's commit-msg hook: see references/hk-steps.pkl.
export default { extends: ["@commitlint/config-conventional"] };
// ESLint flat config — boundary rules only.
// Biome (via Ultracite) owns formatting and general TS linting.
// ESLint's job here is to enforce *architectural* rules Biome can't express:
// 1. Layered `no-restricted-imports` (pure layer ← side-effectful layer)
// 2. `no-restricted-syntax` (raw SQL, raw JSX primitives, dynamic imports outside wrappers)
// 3. Type-assertion bans
// 4. Framework plugins (next, storybook, jsx-a11y)
//
// Paths below (`src/utilities/`, `src/components/`, `src/db/`, etc.) are
// illustrative — adapt to the project's layout. The *pattern* is what matters.
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({ baseDirectory: __dirname });
export default [
...compat.extends(
"next/core-web-vitals",
"next/typescript",
"plugin:jsx-a11y/recommended",
),
// -------- Type safety --------
{
rules: {
// Ban `as Type` entirely. Use `satisfies`, inference, or runtime validation.
// Allowed exceptions (add `eslint-disable-next-line` with a reason comment):
// - `as const` for literal preservation
// - DOM APIs after null checks (e.g. `el as HTMLCanvasElement`)
// - Interop boundaries with untyped libraries
// - Tests intentionally creating invalid data
"@typescript-eslint/consistent-type-assertions": [
"error",
{ assertionStyle: "never" },
],
// Ban `!` non-null assertions — use proper null checks.
"@typescript-eslint/no-non-null-assertion": "error",
// Allow `_foo` unused vars as an intentional opt-out.
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^(_|ignore)",
},
],
// axe-mandated scrollable-region-focusable pattern conflicts with this rule.
"jsx-a11y/no-noninteractive-tabindex": "off",
},
},
// -------- Layer boundary: pure utilities must stay pure --------
// Utilities are meant to be unit-testable with no server/framework coupling.
// If a utility needs server-only APIs, it isn't a utility — move it to
// the intentionally server-coupled layer (e.g. a `queries.ts` barrel).
// Exempt specific files via `ignores` when you need to opt *in* to coupling.
{
files: ["src/utilities/**/*.ts"],
ignores: ["src/utilities/queries.ts", "src/utilities/revalidate.ts"],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "next/cache",
message:
"Utilities must not import server/framework modules — move to the query layer or the calling layer.",
},
{
name: "next/headers",
message:
"Utilities must not import server/framework modules — move to the query layer or the calling layer.",
},
{
name: "next/navigation",
message:
"Utilities must not import server/framework modules — move to the query layer or the calling layer.",
},
// Example: ban direct runtime import of an ORM/CMS SDK from pure code,
// but still allow type imports so shared shapes remain visible.
// {
// name: "<your-orm-or-cms>",
// message: "Move runtime usage to the query layer. Types are allowed.",
// allowTypeImports: true,
// },
],
},
],
},
},
// -------- Layer boundary: UI must not import schema source --------
// Components depend on *generated types*, not *schema definitions*.
// If the UI imports from the schema source, a UI tweak can drag in a DB
// migration and vice-versa. Point UI at the generated types file instead.
{
files: ["src/components/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
group: [
"@/collections/*",
"../collections/*",
"../../collections/*",
],
message:
"Components must not import schema source — use generated types instead.",
},
],
},
],
},
},
// -------- Pattern bans via no-restricted-syntax --------
{
files: ["src/**/*.{ts,tsx}"],
ignores: ["src/db/**", "src/migrations/**"],
rules: {
"no-restricted-syntax": [
"error",
{
// Raw SQL template literals outside the query layer.
// If you need a query, add a function in src/db/ that returns typed rows.
selector: "TaggedTemplateExpression[tag.name='sql']",
message:
"Raw SQL is only allowed in src/db/** — go through the query layer.",
},
{
// The postgres driver itself is off-limits outside src/db/.
selector: "ImportDeclaration[source.value='postgres']",
message:
"The raw postgres driver is only allowed in src/db/**.",
},
{
// Dynamic import() outside a named wrapper. The approved pattern is
// `dynamic(() => import(...), { ssr: false })` via next/dynamic.
// Inline `import()` in arbitrary locations makes chunking hard to reason about.
selector:
"ImportExpression:not([parent.type='ArrowFunctionExpression'])",
message:
"Dynamic import() is only allowed inside next/dynamic or React.lazy wrappers.",
},
],
},
},
// -------- UI hygiene: no raw JSX primitives outside the component library --------
// The design system lives in src/components/ui/. App-level code should import
// from there, not reach for raw <input> / <button> / <a>.
{
files: ["src/app/**/*.{ts,tsx}", "src/components/blocks/**/*.{ts,tsx}"],
rules: {
"no-restricted-syntax": [
"error",
{
selector: "JSXOpeningElement[name.name='input']",
message: "Use <Input /> from src/components/ui/input instead.",
},
{
selector: "JSXOpeningElement[name.name='button']",
message: "Use <Button /> from src/components/ui/button instead.",
},
{
selector: "JSXOpeningElement[name.name='a']",
message: "Use <Link /> from src/components/ui/link instead.",
},
],
},
},
];
# Workspace lint configuration — paste into root Cargo.toml
# Requires Rust 1.74+ (workspace-level lints)
# Each crate needs: [lints]\nworkspace = true
[workspace.lints.clippy]
# Enable pedantic at warn; promote to deny once clean
pedantic = { level = "warn", priority = -1 }
# --- Common allows (adjust per-project) ---
# Numeric code (casts) — allow in emulator/embedded/math-heavy projects
cast-possible-lossless = "allow" # u8 as u16 etc.
cast-possible-truncation = "allow" # intentional width narrowing
cast-precision-loss = "allow" # f64 as f32
cast-sign-loss = "allow" # i32 as u32
# Naming
module-name-repetitions = "allow" # idiomatic Rust (error::Error)
similar-names = "allow" # register names, coordinate pairs
unreadable-literal = "allow" # hex addresses (0x3CD70)
# Documentation (non-library crates only)
missing-errors-doc = "allow"
missing-panics-doc = "allow"
# Low signal-to-noise
must-use-candidate = "allow"
wildcard-imports = "allow" # common in tests and enum re-exports
struct-excessive-bools = "allow" # state/config structs
[workspace.lints.rust]
unsafe_code = "warn" # visible, not denied — FFI crates need escape hatch
// Strict TypeScript compilerOptions — drop this into tsconfig.json
// and keep the comments so future-you knows *why* each flag is set.
{
"compilerOptions": {
// Core: the "strict" family — null checks, no implicit any, etc.
// Non-negotiable. If you find yourself turning sub-flags off, fix the code instead.
"strict": true,
"strictNullChecks": true,
// Indexed access returns T | undefined.
// Prevents `arr[0].foo` crashing on empty arrays; forces explicit checks.
"noUncheckedIndexedAccess": true,
// Fail the build on dead code. Zombie imports and unused vars drift otherwise.
// Prefix with `_` to intentionally keep an unused parameter.
"noUnusedLocals": true,
"noUnusedParameters": true,
// Only allow syntax that can be erased by a pure type-stripper (TS 5.8+).
// Bans `enum`, `namespace`, parameter properties (`constructor(private x)`),
// and other constructs that would require runtime codegen.
// Rationale: interop with deno/bun/swc/esbuild runtimes that strip types
// without a full TS compiler. Migrate enums to `as const` unions.
"erasableSyntaxOnly": true,
// Type-check only — emit is done by bundler / swc / tsgo.
"noEmit": true,
// Catch missed breakage when switching editors or build tools.
"isolatedModules": true,
"skipLibCheck": true,
// Module resolution appropriate for bundler-based projects (Next, Vite, etc.).
"module": "esnext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"resolveJsonModule": true,
"target": "ES2022",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"jsx": "preserve",
"incremental": true
},
"exclude": ["node_modules"]
}