adhere

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Gather adhere rules from a repo

从仓库中收集 adhere 规则

adhere is a linter for rules a normal linter cannot check. Each rule is one sentence in RFC 2119's words plus code that must be written and code that must never be; Jev judges every source file against it and returns a probability. Your job is to find the conventions a repo already has, write the ones that need judgment as rule files, and tune until the report is trustworthy.
adhere 是一种用于检查普通 linter 无法验证的规则的 linter。每条规则由一句使用 RFC 2119 措辞的句子,以及必须编写的代码和绝不能编写的代码组成;Jev 会根据每条规则判断每个源文件,并返回一个概率值。你的任务是找出仓库已有的约定,把需要判断的约定写成规则文件,并持续调优,直到报告结果值得信赖。

1. Survey where conventions live

1. 调查约定所在的位置

Read, in this order, and note every "we always" or "never" statement:
  • AGENTS.md
    ,
    CLAUDE.md
    ,
    CONTRIBUTING.md
    ,
    README.md
    convention sections.
  • docs/
    , ADRs, design notes,
    CONTEXT.md
    .
  • Lint and type configuration (
    oxlint
    ,
    eslint
    ,
    biome
    ,
    tsconfig
    ). These tell you what is already enforced deterministically, so you can exclude it.
  • Recent review feedback:
    gh pr list --state merged --limit 20
    , then
    gh pr view <n> --comments
    on a few. Repeated review comments are unwritten rules.
  • The code. Find the module others copy from: the canonical service, the canonical error type, the canonical test. Those files are your references.
按以下顺序阅读,并记录每一句“我们始终……”或“绝不……”的表述:
  • AGENTS.md
    、
    CLAUDE.md
    、
    CONTRIBUTING.md
    、
    README.md
    中的约定章节。
  • docs/
    、ADR、设计说明、
    CONTEXT.md
    。
  • Lint 和类型配置(
    oxlint
    、
    eslint
    、
    biome
    、
    tsconfig
    )。这些配置表明哪些内容已经通过确定性方式强制执行,因此可以排除。
  • 最近的评审反馈:运行
    gh pr list --state merged --limit 20
    ,再对其中几条运行
    gh pr view <n> --comments
    。重复出现的评审评论就是尚未成文的规则。
  • 代码。找出其他模块会仿照的模块:规范 service、规范 error type、规范 test。这些文件是你的参考。

2. Keep only what needs judgment

2. 仅保留需要判断的规则

For each candidate ask: could a regex, an import check, or the type checker flag every violation? If yes, it belongs in the existing linter, not adhere. Drop it or file it as a lint request.
Keep rules about intent, shape, and placement:
  • "Sequential effectful steps use
    Effect.gen
    , not nested
    flatMap
    ."
  • "A call over the network carries a timeout and a retry schedule."
  • "Business logic depends on a config service, not on
    Config.*
    reads."
  • "Domain errors are
    Schema.TaggedError
    classes, not thrown
    Error
    ."
Skip rules a preset already covers. Run
adhere lint --help
and read
presets/effect/
in the adhere repo before writing Effect rules; extend with
presets: ["effect"]
and add only what is specific to this repo.
对于每个候选规则,先问:regex、import 检查或类型检查器能否标记所有违规?如果可以,它应放在现有 linter 中,而不是 adhere 中。
移除它,或将其作为 lint 需求提交。
保留有关意图、结构和位置的规则:
  • “顺序执行的副作用步骤使用
    Effect.gen
    ,而不是嵌套的
    flatMap
    。”
  • “对网络的调用必须包含 timeout 和 retry schedule。”
  • “业务逻辑依赖 config service,而不是直接读取
    Config.*
    。”
  • “领域错误必须是
    Schema.TaggedError
    类,而不是抛出的
    Error
    。”
跳过 preset 已经覆盖的规则。运行
adhere lint --help
,并在 adhere repo 中阅读
presets/effect/
,然后再编写 Effect 规则;通过
presets: ["effect"]
进行扩展,只添加该仓库特有的内容。

3. Write each rule as a file

3. 将每条规则写为一个文件

Default location
.adhere/<topic>/<slug>.md
, next to the config and the cache. Use
docs/adhere/<topic>/<slug>.md
with
rules: "./docs/adhere"
in the config only when the user wants rules kept with the repo's docs; rules read that way apply project-wide, and nested
.adhere/
directories are not read. The path without
.md
is the rule id.
md
---
description: One sentence saying what code must be, then what it must never be.
threshold: 0.8
---

Optional prose for readers on GitHub. adhere ignores it.
默认位置为
.adhere/<topic>/<slug>.md
,与配置和缓存相邻。仅当用户希望规则与仓库文档放在一起时,才使用
docs/adhere/<topic>/<slug>.md
,并在配置中设置
rules: "./docs/adhere"
;以这种方式读取的规则适用于整个项目,嵌套的
.adhere/
目录不会被读取。不带
.md
的路径即为 rule id。
md
---
description: One sentence saying what code must be, then what it must never be.
threshold: 0.8
---

Optional prose for readers on GitHub. adhere ignores it.

Must

Must

ts
// Correct code lifted from this repo, trimmed to the pattern.
ts
// Correct code lifted from this repo, trimmed to the pattern.

Never

Never

ts
// Optional: the incorrect form this rule catches, as it appeared in the repo.

Rules for the rule:

- `description` is one sentence, specific, in the same words as the headings:
  what code must be, and what it must never be. Jev reads the description
  with the code, and "must" in both is one demand; vague words ("properly",
  "correctly") give it nothing to judge.
- A heading names the code under it only when it is the word alone: `## Never`,
  not `## Never do this`. The next heading at its level or higher ends it.
- The `must` block is real code from the repo, not invented, and only correct
  code: Jev compares files to it, so incorrect code there teaches the wrong
  thing. Incorrect code goes under `## Never`, which Jev reads as what a
  violation looks like. Add one whenever violations have a recognizable
  shape, ideally one found in the repo's history: over the effect preset, a
  `never` block raised how well Jev told violations from compliant code under
  every wording tried. A rule with no single correct form can be only a
  `never` block.
- A convention that is a guideline rather than a requirement says "should" in
  its description and puts its code under `## Should` and `## Should not`. A
  rule is one or the other; it cannot mix the two.
- One pattern per file. Two patterns in one `must` block blur the probability.
- Jev reads code without its comments, except comments that say `@adhere`. A
  rule about comments, such as doc comments on exports, needs
  `includeComments: true` in the config.
- Rules skip tests: `.test.ts` and `.spec.ts` files, and files under `test/`,
  `tests/`, `__tests__/`, or `fixtures/`. A rule about tests says `tests: only`
  in its front matter, and one that holds in tests as well `tests: include`.
- A nit, or a rule that tends to flag code wrongly, says `level: warning` in
  its front matter: its findings show in amber and do not fail CI.
- `threshold` is optional. Start without it; set it in step 5 if needed.
ts
// Optional: the incorrect form this rule catches, as it appeared in the repo.

规则编写要求:

- `description` 必须是具体的一句话,并沿用标题中的措辞:说明代码必须是什么,以及代码绝不能是什么。Jev 会将 `description` 与代码一起读取;如果两处都使用“must”,则表示同一个要求;“正确地”“妥善地”等模糊措辞无法为它提供任何判断依据。
- 只有当标题仅由对应词语构成时,它才限定其下的代码:使用 `## Never`,而不是 `## Never do this`。同级或更高层级的下一个标题会结束该代码块。
- `must` 代码块应使用仓库中的真实代码,而不是虚构代码,并且只能包含正确代码:Jev 会将文件与它比较,因此把错误代码放在这里会教会 Jev 错误的模式。错误代码应放在 `## Never` 下,Jev 会将其理解为违规代码的样子。只要违规具有可识别的形态,就应添加一个错误示例,最好使用仓库历史中找到的示例:在 effect preset 的基础上,添加 `never` 代码块后,在尝试过的每种表述下,Jev 区分违规代码与合规代码的能力都更强。若某条规则不存在唯一正确形式,则可以只设置 `never` 代码块。
- 如果某项约定只是指导原则而非硬性要求,应在 `description` 中使用“should”,并将代码分别放在 `## Should` 和 `## Should not` 下。一条规则只能是二者之一,不能将两类混用。
- 每个文件只包含一种模式。在一个 `must` 代码块中放入两种模式会模糊概率判断。
- Jev 读取代码时会忽略注释,但带有 `@adhere` 的注释除外。如果规则针对注释,例如要求导出项必须有 doc comment,则需要在配置中设置 `includeComments: true`。
- 规则默认跳过测试:包括 `.test.ts` 和 `.spec.ts` 文件,以及 `test/`、`tests/`、`__tests__/` 或 `fixtures/` 目录下的文件。专门针对测试的规则在 front matter 中写 `tests: only`;如果规则在测试中也成立,则写 `tests: include`。
- 吹毛求疵的规则,或容易误报的规则,应在 front matter 中写 `level: warning`:其 finding 会以琥珀色显示,并且不会使 CI 失败。
- `threshold` 是可选的。开始时可以不设置;如有必要,可在第 5 步中设置。

4. Configure

4. 配置

Put the config at
.adhere/config.ts
.
adhere.config.ts
and
.adhere.config.ts
also load; keep one. A config is optional when
.adhere/
exists or a preset is passed on the command line.
adhere init
scaffolds it with
defineConfig
, adds
@drkmttr/adhere
as a dev dependency, and writes
.adhere/tsconfig.json
so the editor resolves its types; the native binary supplies the import either way:
ts
import { defineConfig } from "@drkmttr/adhere";

export default defineConfig({
  presets: ["effect"],
  threshold: 0.75,
  exclude: ["**/generated/**"],
});
To quiet a preset rule that does not suit the repo, override it rather than copy it:
overrides: { "alchemy/providers/idempotent-delete": "warning" }
, or
"off"
, or
{ threshold: 0.9 }
. The id is the one the report shows.
exclude
lists globs, from the working directory, of files no rule judges. Exclude generated and vendored code the skipped directories do not already cover, since findings there are noise no one will fix.
Commit
.adhere/cache/
with the code, so the team and CI reuse its judgments instead of paying for them again. Uncached files need a TypeSafe AI API key: one saved by
adhere login
, or
TYPESAFE_API_KEY
, which takes precedence; in this org it comes from SOPS via
just shell
.
将配置放在
.adhere/config.ts
。
adhere.config.ts
和
.adhere.config.ts
也会被加载;确保只保留一个。当
.adhere/
存在或命令行传入 preset 时,配置可以省略。
adhere init
会通过
defineConfig
生成配置骨架,将
@drkmttr/adhere
添加为 dev dependency,并写入
.adhere/tsconfig.json
,让编辑器能够解析其类型;无论是否进行上述配置,native binary 都会提供该 import:
ts
import { defineConfig } from "@drkmttr/adhere";

export default defineConfig({
  presets: ["effect"],
  threshold: 0.75,
  exclude: ["**/generated/**"],
});
如果某个 preset 规则不适合当前仓库,应覆盖它而不是复制它:使用
overrides: { "alchemy/providers/idempotent-delete": "warning" }
、
"off"
或
{ threshold: 0.9 }
。rule id 即报告中显示的 id。
exclude
列出以工作目录为基准的 glob,表示不应对哪些文件进行规则判断。对于跳过目录未覆盖的生成代码和 vendored code,也应将其排除,因为这些位置的 finding 只是无人会修复的噪声。
将
.adhere/cache/
与代码一同提交,让团队和 CI 复用已有的判断结果,而不必再次为这些结果付费。未缓存的文件需要 TypeSafe AI API key:可以通过
adhere login
保存一个,也可以使用
TYPESAFE_API_KEY
,后者的优先级更高;在本组织中,它通过 SOPS 由
just shell
提供。

5. Run and calibrate

5. 运行并校准

sh
adhere validate              # decodes the config and rules; reports their wording, rules a linter could check, and contradictions
adhere lint                  # or: adhere lint --preset effect
adhere lint --threshold 0.9  # re-reads the cache, sends nothing
Then, per rule, read the findings and decide:
  • Many hits at 0.80 to 0.90, mostly not violations: the description is too broad. Narrow it (say what is out of scope) or raise that rule's
    threshold
    . Prefer narrowing; a threshold hides, a sentence explains.
  • Hits on code that follows the pattern through a different API: the
    must
    block is too specific. Use the repo's most general correct example.
  • Zero hits: plant a deliberate violation in a scratch file, run, confirm the rule fires, remove the file. A rule that cannot fire is not a rule.
  • Hits at 0.9 and above: read them first. They are usually real.
To verify and fix the findings themselves, one at a time, use the adhere-fix skill, which
adhere skill fix
prints.
Threshold changes never re-judge; only edited rules do (per rule) and edited files do (per file). Iterate on wording freely, it costs one request per file per changed rule.
sh
adhere validate              # decodes the config and rules; reports their wording, rules a linter could check, and contradictions
adhere lint                  # or: adhere lint --preset effect
adhere lint --threshold 0.9  # re-reads the cache, sends nothing
然后,逐条规则阅读 findings 并作出判断:
  • 在 0.80 到 0.90 之间出现大量命中,且多数不是违规:说明
    description
    过于宽泛。应收窄规则,例如明确排除哪些情况,或提高该规则的
    threshold
    。优先收窄描述;阈值只能隐藏问题,句子则能解释规则。
  • 通过不同 API 实现该模式的代码也被命中:说明
    must
    代码块过于具体。应使用仓库中最具通用性的正确示例。
  • 完全没有命中:在临时文件中故意加入一处违规,运行检查,确认规则能够触发,然后删除该文件。无法触发的规则不算有效规则。
  • 命中概率达到 0.9 或更高:应优先检查这些结果,它们通常确实是违规。
要逐项核验并修复这些问题,请使用 adhere-fix skill;
adhere skill fix
会打印该 skill 的说明。
修改阈值永远不会触发重新判断;只有规则被编辑时,才会逐条规则重新判断,只有文件被编辑时,才会逐文件重新判断。可以自由迭代措辞;每变更一条规则,每个文件只会产生一次请求。

6. Report

6. 汇报

Give the user: the rule ids written and where, the config path, the finding count at the chosen threshold and at 0.9, and the rules you dropped as deterministic with where they belong instead.
向用户提供:已编写的 rule id 及其位置、配置路径、在所选阈值和 0.9 下的 finding 数量,以及被判定为确定性问题而移除的规则及其更合适的归属。