mechanical-enforcement

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

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.
原本需要评审人员牢记的规则都应该放到linter中。本skill是经过精心整理的规则目录,包含执行这些规则的linter工具以及每条规则的设计理由,这样你无需从头梳理规则集就能完成新项目的代码加固。
这是一个内容类skill,而非工具。它提供规则和代码片段,如果你需要将这些规则配置到git hooks中,请参考
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.
  1. 机制优先于人为约定。如果一条规则需要靠评审人员牢记,迟早会出现执行偏移。将规则编码到linter、类型系统或测试中,永远不要只停留在口头约定层面。
  2. 类型优先,lint次之,测试最后。优先使用
    strict
    模式的TypeScript / Pydantic / clippy,而非自定义lint规则。当类型系统无法表达规则时再使用lint规则,只有前两者都无法实现时才用测试覆盖。
  3. 架构边界就是linter规则。层级约束(领域层 ← 基础设施层、工具层 ← 服务端、UI ← schema定义)通过
    no-restricted-imports
    /
    no-restricted-syntax
    规则强制执行,不能依赖开发人员的自觉性。
  4. 可自动修复的优先自动修复,不可修复的设置门禁。格式化工具和空格修复工具开启
    fix = true
    执行后自动重新暂存文件,正确性相关规则作为代码提交的门禁校验项。
  5. 优先使用有主见的预设配置,最小化自定义覆盖。Biome用Ultracite预设,提交信息用
    @commitlint/config-conventional
    ,Next项目用
    next/core-web-vitals
    。仅当有明确理由时才覆盖默认配置,且必须添加注释说明原因。
  6. 规则的设计理由与规则共存。每一条非显而易见的覆盖配置都要有行内注释,说明如果移除该配置会导致什么问题。

When to use this skill

何时使用本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.
  • 新项目配置lint规则 → 从下表中选择linter,从
    references/
    目录复制代码片段,通过
    hk
    skill完成配置。
  • 加固现有项目 → 对照规则目录进行审计,补充缺失的规则。
  • 刚出现bug时 → 思考"什么样的规则可以从机制上提前捕获这类问题?",并将对应规则添加到本目录中。
  • 为不熟悉的技术栈选择linter → 参考选型表。

Linter picks by stack

按技术栈分类的linter选型

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
strict
Ultracite is the default for new projects. Raw Biome only if Ultracite doesn't support the framework.
TypeScript (library / node)BiomeBiome
tsc --noEmit
strict
Skip ESLint entirely unless you need boundary rules.
Pythonruff formatruffbasedpyright strict (or pyright)
ruff
replaces black + isort + flake8 + pylint.
Rustrustfmtclippy (
-D warnings
)
cargo check
clippy::pedantic
selectively; full pedantic is too noisy.
Gogofmt / gofumptgolangci-lint
go vet
Enable
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.
优先使用首选列的工具;仅当首选工具无法实现对应规则时才使用备选列的工具。
技术栈格式化工具首选linter备选工具类型检查说明
TypeScript / React / NextBiome(通过Ultracite预设
core
react
next
BiomeESLint flat config — 仅用于
no-restricted-imports
no-restricted-syntax
jsx-a11y
、框架插件(next、storybook)
tsc --noEmit
strict
新项目默认使用Ultracite,仅当Ultracite不支持对应框架时才直接使用原生Biome。
TypeScript(类库/node)BiomeBiome
tsc --noEmit
strict
除非需要边界规则,否则完全不需要ESLint。
Pythonruff formatruffbasedpyright strict(或pyright)
ruff
可以替代black + isort + flake8 + pylint。
Rustrustfmtclippy(
-D warnings
cargo check
可选择性开启
clippy::pedantic
,全量pedantic规则噪音过多。
Gogofmt / gofumptgolangci-lint
go vet
开启
errcheck
govet
staticcheck
revive
Shellshfmtshellcheck仅当有注释说明时才添加
-e SC2086
Markdownrumdlrumdl同时支持frontmatter校验。
Nixnixfmtdeadnix + statix
YAMLyamllint
提交信息commitlint(
@commitlint/config-conventional
单行配置,参考
references/commitlint.config.js
密钥gitleaks必须添加,成本低、检出准确率高。
拼写错误typos速度快、支持自动修复、误报率极低。

Rules catalogue

规则目录

Rules are organised by concern, not by linter. Each entry gives: what it prevents, how to encode it, and known exceptions.
规则按关注点而非linter分类,每个条目包含:预防的问题、实现方式、已知例外。

Type safety

类型安全

RuleEncode withPreventsNotes
Full strict mode
tsconfig.json
:
"strict": true
Most null/undefined footgunsNon-negotiable.
Indexed access returns
T | undefined
"noUncheckedIndexedAccess": true
arr[0].foo
crashing on empty arrays
See
references/typescript-strict.jsonc
.
Dead code fails build
"noUnusedLocals": true
,
"noUnusedParameters": true
Drifted 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-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 systemUse
unknown
+ narrowing.
No
as Type
assertions
ESLint
@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 assertion
ESLint
@typescript-eslint/no-non-null-assertion
Silent runtime crashesUse a proper null check or throw a narrowed error.
Prefer
import type
Biome
useImportType
Accidental runtime imports of type-only modulesAuto-fixable.
规则实现方式预防的问题说明
全量strict模式
tsconfig.json
:
"strict": true
绝大多数null/undefined相关的坑必须开启,无例外。
索引访问返回
T | undefined
"noUncheckedIndexedAccess": true
空数组场景下
arr[0].foo
导致的崩溃
参考
references/typescript-strict.jsonc
死代码导致构建失败
"noUnusedLocals": true
,
"noUnusedParameters": true
漂移的导入、僵尸变量故意保留未使用参数时加
_
前缀。
仅使用可擦除的TS语法
"erasableSyntaxOnly": true
(TS 5.8+)
enum
namespace
、构造函数参数属性等无法通过纯类型擦除保留的语法
无需TS运行时即可实现deno/bun/swc/esbuild兼容,会破坏现有使用
enum
的代码,建议迁移到
as const
联合类型。
禁止使用
any
Biome
noExplicitAny
(错误级别)
绕过类型系统使用
unknown
+ 类型收窄。
禁止使用
as Type
类型断言
ESLint
@typescript-eslint/consistent-type-assertions
搭配
assertionStyle: "never"
对编译器的静默欺骗允许的例外(每个例外都需要加
eslint-disable-next-line
+ 理由):
as const
、空校验后的DOM API、非类型化类库兼容、故意构造的无效测试用例。
禁止使用
!
非空断言
ESLint
@typescript-eslint/no-non-null-assertion
静默的运行时崩溃使用规范的空值检查或者抛出收窄后的错误。
优先使用
import type
Biome
useImportType
意外导入仅类型模块的运行时代码支持自动修复。

Error handling

错误处理

RuleEncode withPreventsNotes
No bare
catch
/ swallowed errors
Biome
noCatchAssign
,
useErrorMessage
; ESLint
no-empty
with
allowEmptyCatch: false
Errors 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 code
Biome
noConsole
with
allow: ["warn", "error"]
Logs leaking to user consolesUse the project's logger.
规则实现方式预防的问题说明
禁止空
catch
/吞掉错误
Biome
noCatchAssign
useErrorMessage
;ESLint
no-empty
搭配
allowEmptyCatch: false
错误凭空消失在catch中做类型收窄(
catch (e) { if (e instanceof FooError) ... }
)或者重新抛出。
禁止不带cause的全捕获重抛自定义
no-restricted-syntax
捕获不带
{ cause }
的重抛逻辑
丢失错误上下文要求的写法:
throw new Error("while doing X", { cause: e })
领域边界优先使用Result类型约定 + 评审;无对应linter规则纯代码中基于异常的控制流异常仅允许出现在命令式外壳层。
生产代码禁止使用
console.*
Biome
noConsole
搭配
allow: ["warn", "error"]
日志泄漏到用户控制台使用项目统一的日志工具。

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
.
使用
no-restricted-imports
no-restricted-syntax
让非法的依赖图无法编译。模式目录如下:
  • 纯逻辑层不能导入有副作用的层
    files: ["src/utilities/**"]
    +
    no-restricted-imports
    禁止导入
    next/cache
    next/headers
    next/navigation
    、ORM运行时模块。对仍需要可见的类型设置
    allowTypeImports: true
    。通过
    ignores
    豁免少数故意耦合的文件(
    queries.ts
    revalidate.ts
    )。
  • UI不能直接导入schema
    files: ["src/components/**"]
    +
    no-restricted-imports patterns
    禁止导入
    @/collections/*
    (或者你存放DB schema的路径)。UI应该依赖生成的类型,而非schema源码——否则UI修改会触发不必要的迁移。
  • 仅查询层允许写原生SQL。除了
    src/db/**
    之外的所有位置通过
    no-restricted-syntax
    禁止
    TaggedTemplateExpression[tag.name='sql']
    语法。同时禁止同一目录外导入原生驱动(
    ImportDeclaration[source.value='postgres']
    )。
  • 动态
    import()
    仅允许通过命名包装器使用
    。除了
    next/dynamic
    /
    React.lazy
    之外通过
    no-restricted-syntax
    禁止
    ImportExpression
    语法。防止破坏SSR的临时代码分片。
完整可用的代码片段在
references/eslint-boundaries.mjs
中。

UI hygiene (React / Next)

UI卫生规范(React / Next)

RuleEncode withPreventsNotes
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 systemExempt 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 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:/)
.
规则实现方式预防的问题说明
组件库外禁止使用原生
<input>
/
<button>
/
<a>
在业务代码中通过
no-restricted-syntax
禁止
JSXOpeningElement[name.name='input']
(等元素)
脱离设计系统的样式漂移豁免UI库路径(
src/components/ui/**
),错误信息指向对应的包装组件。
开启
jsx-a11y/recommended
通过flat config开启ESLint
plugin:jsx-a11y/recommended
可访问性退化关闭
no-noninteractive-tabindex
——axe要求的
scrollable-region-focusable
模式与该规则冲突。
禁止行内样式Biome
noInlineStyles
(或ESLint
react/forbid-dom-props
绕过设计系统允许少数图表组件使用
style
,但需要加禁用注释说明。
useTopLevelRegex
(Biome)
Ultracite默认开启正则表达式每次调用都重新编译;测试断言中使用行内正则优先使用
.toThrow("Cannot submit:")
而非
.toThrow(/Cannot submit:/)

Import hygiene

导入卫生规范

RuleEncode withPrevents
Sorted + grouped importsBiome
organizeImports
on format
Merge conflicts; inconsistency
No cyclesmadge (
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.
规则实现方式预防的问题
导入排序+分组格式化时开启Biome
organizeImports
合并冲突;风格不一致
禁止循环依赖pre-commit中执行madge
madge --circular
)或者
eslint-plugin-import
no-cycle
规则
模块初始化顺序bug
禁止默认导出(可选)Biome
noDefaultExport
/ ESLint
import/no-default-export
导入位置命名不一致;重命名重构体验差。豁免Next.js要求默认导出的页面/布局文件。
唯一函数名同一文件内通过
no-restricted-syntax
禁止重复的
FunctionDeclaration
标识符;兜底方案是基于grep的hk步骤
重复编写功能重合的辅助函数而没有复用已有的实现。Grep检查可以覆盖ESLint无法处理的跨文件场景。

Testing

测试

RuleEncode withPrevents
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 assertionsBiome
useTopLevelRegex
Flaky 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
规则实现方式预防的问题
禁止提交
.only
Biome
noFocusedTests
(Ultracite默认开启);或者ESLint
vitest/no-focused-tests
CI中意外跳过其余测试用例
断言中禁止使用行内正则Biome
useTopLevelRegex
匹配不稳定、错误信息不清晰
pre-commit强制覆盖率阈值hk步骤执行
vitest run --coverage
+ vitest配置
thresholds: { 100: true }
未测试的分支流入代码库。对无法到达的防御性代码使用
/* v8 ignore next */
豁免。
单元测试禁止使用mock约定 + 评审测试通过但掩盖了集成bug

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_before
Compromised releases
No
--no-verify
Documented in project CLAUDE.md / AGENTS.md; not technically preventableBypassing the whole gate. Cultural rule — reinforce in every project's agent docs.
规则实现方式预防的问题
禁止提交密钥gitleaks pre-commit步骤Token泄漏
依赖版本固定+新包隔离pnpm
minimum-release-age
、npm
min-release-age
、uv
exclude-newer
、mise
install_before
被篡改的版本发布
禁止使用
--no-verify
在项目CLAUDE.md / AGENTS.md中说明;无法从技术上禁止绕过所有门禁校验。属于文化规则——在每个项目的agent文档中强调。

Commit messages

提交信息

js
// 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.
js
// commitlint.config.js
export default { extends: ["@commitlint/config-conventional"] };
通过hk的
commit-msg
钩子配置(参考
references/hk-steps.pkl
),无需其他配置。

Composition with the
hk
skill

hk
skill配合使用

This skill gives you what to enforce. The
hk
skill gives you how to wire it.
The typical mapping:
tier 1 (format/fix)     → trailing-whitespace, newlines, typos, rumdl, biome fix
tier 2 (lint/gate)      → biome check, eslint, gitleaks, yamllint, check-merge-conflict
tier 3 (typecheck)      → tsc --noEmit (or tsgo)
tier 4 (test)           → vitest run --coverage
commit-msg              → commitlint
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.
本skill告诉你要强制执行什么
hk
skill告诉你怎么配置这些规则
典型的映射关系:
tier 1 (格式化/自动修复)     → trailing-whitespace, newlines, typos, rumdl, biome fix
tier 2 (lint/门禁校验)      → biome check, eslint, gitleaks, yamllint, check-merge-conflict
tier 3 (类型检查)      → tsc --noEmit (或tsgo)
tier 4 (测试)           → vitest run --coverage
commit-msg              → commitlint
在pre-commit中设置
fix = true
+
stash = "git"
,这样tier 1的规则自动修复后会重新暂存文件。完整示例参考
references/hk-steps.pkl

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/
    .
当有bug流到评审阶段或生产环境时,复盘需要问的问题是:什么样的规则可以从机制上提前捕获这个问题?
  1. 找出可以表达该规则的最小AST模式、导入路径或者类型标记。
  2. 选择负责对应领域的linter(参考选型表)。
  3. 添加规则,同时加行内注释说明它预防的故障模式。
  4. 在上方对应规则目录板块添加条目,附上同样的设计理由(在本SKILL.md中)。
  5. 如果是值得分享的新类型规则,在
    references/
    目录添加对应的代码片段。

References

参考资料

  • 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/hk-steps.pkl
    — worked hk.pkl step graph
  • references/commitlint.config.js
    — one-line conventional-commits config
  • Ultracite — Biome preset bundle
  • hk — git hook manager
  • references/typescript-strict.jsonc
    — 严格模式
    compilerOptions
    配置块(可直接复用)
  • references/biome-ultracite.jsonc
    — 扩展Ultracite的Biome配置,包含覆盖规则的示例
  • references/eslint-boundaries.mjs
    — 分层
    no-restricted-imports
    +
    no-restricted-syntax
    示例
  • references/hk-steps.pkl
    — 完整可用的hk.pkl步骤图
  • references/commitlint.config.js
    — 单行约定式提交配置
  • Ultracite — Biome预设集合
  • hk — git hook管理器