Comprehensive guide to the enhanced strict linting rules in dev-config, covering TypeScript type safety, Effect-TS patterns, infrastructure validation, and code quality enforcement.
This configuration provides:
- 80+ Biome rules for strict TypeScript/JavaScript linting
- GritQL custom patterns for Effect-TS and async safety
- TypeScript strict configs beyond
strict: true - IaC validation for Kubernetes, Terraform, Dockerfiles, GitHub Actions
- Pre-commit hooks for automated enforcement
# Apply all linting configurations
home-manager switch --flake ~/Projects/dev-config
# Verify tools are available
biome --version
hadolint --version
actionlint --versionWe prioritize enforcing correct patterns (use* rules) over banning wrong patterns (no* rules):
| Approach | Example | Why |
|---|---|---|
| ✅ Direct Equality | useImportType: error |
Enforces import type { X } |
noCommonJs: error |
Bans require() as safety net |
This reduces cognitive load - developers learn what TO do, not what NOT to do.
TypeScript strict settings should be configured in each project's tsconfig.base.json. Recommended strict options:
{
"noUncheckedIndexedAccess": true, // arr[0] returns T | undefined
"exactOptionalPropertyTypes": true, // Optional props can't be undefined
"noPropertyAccessFromIndexSignature": true,
"noImplicitOverride": true, // Explicit override keyword
"useUnknownInCatchVariables": true // catch(e: unknown) not any
}Trade-offs:
noUncheckedIndexedAccess- Requires null checks for all array accessexactOptionalPropertyTypes- May break existing{ prop?: T }patterns
{
"noBarrelFile": "error", // Ban index.ts re-exports
"noReExportAll": "error", // Ban export * from
"noAccumulatingSpread": "error" // Ban spreading in loops
}Why barrel files are banned: Atlassian achieved 75% faster builds by eliminating barrel files. They cause unnecessary module loading and break tree-shaking.
{
"useImportType": "error", // import type { X }
"useExportType": "error", // export type { X }
"useNodejsImportProtocol": "error", // import 'node:fs'
"noCommonJs": "error" // Ban require()
}{
"noExcessiveCognitiveComplexity": {
"level": "error",
"options": { "maxAllowedComplexity": 15 }
}
}Cognitive complexity > cyclomatic complexity because it accounts for nesting depth.
{
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
"extends": ["~/.config/biome/biome.json"],
"linter": {
"rules": {
"suspicious": {
"noConsole": "off" // Override for development
}
}
}
}{
"overrides": [
{
"includes": ["**/*.test.ts"],
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "off"
}
}
}
}
]
}`Effect.$m1(Effect.$m2(Effect.$m3($args)))` where {
register_diagnostic(
message = "Use Effect.pipe() for composition",
severity = "error"
)
}
Example:
// ❌ Bad - flagged
const result = Effect.map(Effect.flatMap(Effect.succeed(x), f), g);
// ✅ Good
const result = Effect.pipe(
Effect.succeed(x),
Effect.flatMap(f),
Effect.map(g)
);This pattern prevents one of the most common Effect-TS bugs:
// ❌ Bad - x is Effect<number>, not number!
Effect.gen(function* () {
const x = Effect.succeed(42);
// ^ x is Effect, not the value
});
// ✅ Good - x is number
Effect.gen(function* () {
const x = yield* Effect.succeed(42);
});
⚠️ Expert Validated: GritQL provides ~60-70% coverage for Effect-TS patterns. It cannot access TypeScript type information, so type-aware rules like "Effect not awaited" are not possible. For production codebases, consider adding@effect/eslint-plugin.
All hooks run automatically on git commit:
repos:
- repo: local
hooks:
- id: biome-check # JS/TS/JSON
- id: nix-fmt # Nix formatting
- id: nix-flake-check # Nix validation
- id: validate-linting-config # AI guardrails# All hooks
pre-commit run --all-files
# Specific hook
pre-commit run biome-check --all-files- Remove ESLint config files (
.eslintrc.*) - Remove ESLint dependencies from
package.json - Update scripts:
{ "scripts": { "lint": "biome check .", "lint:fix": "biome check --write ." } } - Extend from dev-config Biome config:
{ "extends": ["~/.config/biome/biome.json"] }
If your codebase uses barrel files:
-
Option 1: Disable temporarily:
{ "overrides": [ { "includes": ["**/index.ts"], "linter": { "rules": { "performance": { "noBarrelFile": "off" } } } } ] } -
Option 2: Migrate incrementally:
- Replace
export * fromwith explicit exports - Move exports directly to consumer modules
- Run
biome check --writeto fix import paths
- Replace
If enabling noUncheckedIndexedAccess:
// Before
const first = arr[0]; // T
// After - requires null check
const first = arr[0]; // T | undefined
if (first !== undefined) {
// first is T
}
// Or use optional chaining
const value = arr[0]?.property;name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: nix develop -c pre-commit run --all-files# Required checks
- biome-check
- kube-linter
- hadolint
- tflint
- actionlint
- gitleaks"Rule X does not exist":
Check Biome version matches schema:
{
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json"
}Performance issues:
Use --diagnostic-level=error to skip warnings during development.
"command not found":
Ensure Nix devShell is active:
nix develop
pre-commit run --all-filesHook taking too long:
Skip specific hooks for quick commits:
SKIP=kube-linter git commit -m "WIP"Pattern not matching:
GritQL uses JavaScript AST, not TypeScript. Some TS-specific syntax may not match. Test patterns at https://grit.io/playground.
| Severity | Meaning | Auto-fixable |
|---|---|---|
error |
Must fix before commit | Most |
warn |
Should fix, not blocking | Some |
info |
Informational | No |
| File | Purpose |
|---|---|
biome.json |
Biome rule configuration (source of truth) |
biome/gritql-patterns/*.grit |
Custom GritQL rules |
.pre-commit-config.yaml |
Pre-commit hooks |
pkgs/default.nix |
Linting tool packages |