@drkmttr/adhere 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +149 -51
  2. package/package.json +9 -9
  3. package/presets/effect/basics/{instrument-with-pipe.md → instrument-with-pipe/RULE.md} +1 -1
  4. package/presets/effect/config/{business-logic-depends-on-config-service.md → business-logic-depends-on-config-service/RULE.md} +3 -3
  5. package/presets/effect/config/{secrets-are-redacted.md → secrets-are-redacted/RULE.md} +3 -3
  6. package/presets/effect/config/{validate-with-schema.md → validate-with-schema/RULE.md} +4 -4
  7. package/skills/adhere/SKILL.md +206 -118
  8. package/skills/adhere-fix/SKILL.md +9 -8
  9. package/skills/adhere-setup/SKILL.md +244 -0
  10. package/src/cli.ts +49 -33
  11. package/src/config.ts +50 -3
  12. package/src/index.ts +2 -0
  13. package/src/init.ts +36 -25
  14. package/src/install.ts +37 -26
  15. package/src/main.ts +1 -1
  16. package/src/rules.ts +135 -56
  17. package/src/services/AdhereConfig.ts +2 -2
  18. package/src/services/Credentials.ts +4 -4
  19. package/src/services/Jev.http.ts +76 -10
  20. package/src/services/Jev.ts +39 -10
  21. package/src/workflows/audit.ts +23 -12
  22. package/src/workflows/format.ts +172 -35
  23. /package/presets/alchemy/apis/{match-remote-errors-by-tag.md → match-remote-errors-by-tag/RULE.md} +0 -0
  24. /package/presets/alchemy/apis/{schemas-at-trust-boundaries.md → schemas-at-trust-boundaries/RULE.md} +0 -0
  25. /package/presets/alchemy/data/{commit-migrations-then-deploy.md → commit-migrations-then-deploy/RULE.md} +0 -0
  26. /package/presets/alchemy/data/{hyperdrive-direct-origin.md → hyperdrive-direct-origin/RULE.md} +0 -0
  27. /package/presets/alchemy/data/{migrations-use-direct-url.md → migrations-use-direct-url/RULE.md} +0 -0
  28. /package/presets/alchemy/data/{never-disable-tls-verification.md → never-disable-tls-verification/RULE.md} +0 -0
  29. /package/presets/alchemy/data/{private-endpoint-at-runtime.md → private-endpoint-at-runtime/RULE.md} +0 -0
  30. /package/presets/alchemy/data/{single-migration-owner.md → single-migration-owner/RULE.md} +0 -0
  31. /package/presets/alchemy/durable/{idempotent-event-handlers.md → idempotent-event-handlers/RULE.md} +0 -0
  32. /package/presets/alchemy/durable/{io-inside-steps.md → io-inside-steps/RULE.md} +0 -0
  33. /package/presets/alchemy/durable/{no-self-triggering-bucket-writes.md → no-self-triggering-bucket-writes/RULE.md} +0 -0
  34. /package/presets/alchemy/durable/{stable-step-names.md → stable-step-names/RULE.md} +0 -0
  35. /package/presets/alchemy/durable/{webhook-state-is-truth.md → webhook-state-is-truth/RULE.md} +0 -0
  36. /package/presets/alchemy/providers/{diff-guards-unresolved-props.md → diff-guards-unresolved-props/RULE.md} +0 -0
  37. /package/presets/alchemy/providers/{idempotent-delete.md → idempotent-delete/RULE.md} +0 -0
  38. /package/presets/alchemy/providers/{lazy-layer-construction.md → lazy-layer-construction/RULE.md} +0 -0
  39. /package/presets/alchemy/providers/{reconcile-from-observed-state.md → reconcile-from-observed-state/RULE.md} +0 -0
  40. /package/presets/alchemy/resources/{anchor-paths-with-import-meta.md → anchor-paths-with-import-meta/RULE.md} +0 -0
  41. /package/presets/alchemy/resources/{idempotent-actions-and-seeds.md → idempotent-actions-and-seeds/RULE.md} +0 -0
  42. /package/presets/alchemy/resources/{previews-reference-shared-resources.md → previews-reference-shared-resources/RULE.md} +0 -0
  43. /package/presets/alchemy/resources/{protect-irreplaceable-data.md → protect-irreplaceable-data/RULE.md} +0 -0
  44. /package/presets/alchemy/resources/{remote-state-for-ci.md → remote-state-for-ci/RULE.md} +0 -0
  45. /package/presets/alchemy/resources/{stable-logical-ids.md → stable-logical-ids/RULE.md} +0 -0
  46. /package/presets/alchemy/resources/{stage-unique-physical-names.md → stage-unique-physical-names/RULE.md} +0 -0
  47. /package/presets/alchemy/runtime/{bind-tag-not-implementation.md → bind-tag-not-implementation/RULE.md} +0 -0
  48. /package/presets/alchemy/runtime/{deterministic-config-defaults.md → deterministic-config-defaults/RULE.md} +0 -0
  49. /package/presets/alchemy/runtime/{disposables-in-request-scope.md → disposables-in-request-scope/RULE.md} +0 -0
  50. /package/presets/alchemy/runtime/{lambda-no-post-response-work.md → lambda-no-post-response-work/RULE.md} +0 -0
  51. /package/presets/alchemy/runtime/{no-per-request-work-at-construction.md → no-per-request-work-at-construction/RULE.md} +0 -0
  52. /package/presets/alchemy/runtime/{resolve-config-and-bindings-at-construction.md → resolve-config-and-bindings-at-construction/RULE.md} +0 -0
  53. /package/presets/alchemy/secrets/{let-alchemy-mint-infra-secrets.md → let-alchemy-mint-infra-secrets/RULE.md} +0 -0
  54. /package/presets/alchemy/secrets/{none-in-browser-env.md → none-in-browser-env/RULE.md} +0 -0
  55. /package/presets/alchemy/secrets/{redacted-until-use.md → redacted-until-use/RULE.md} +0 -0
  56. /package/presets/alchemy/secrets/{unwrap-before-compare-or-send.md → unwrap-before-compare-or-send/RULE.md} +0 -0
  57. /package/presets/alchemy/security/{authorize-public-endpoints.md → authorize-public-endpoints/RULE.md} +0 -0
  58. /package/presets/alchemy/security/{github-secret-not-variable.md → github-secret-not-variable/RULE.md} +0 -0
  59. /package/presets/alchemy/security/{identity-from-verified-session.md → identity-from-verified-session/RULE.md} +0 -0
  60. /package/presets/alchemy/security/{keep-auth-protections-on.md → keep-auth-protections-on/RULE.md} +0 -0
  61. /package/presets/alchemy/security/{least-privilege-bindings.md → least-privilege-bindings/RULE.md} +0 -0
  62. /package/presets/alchemy/security/{least-privilege-ci-credentials.md → least-privilege-ci-credentials/RULE.md} +0 -0
  63. /package/presets/alchemy/security/{lookup-failure-is-not-anonymous.md → lookup-failure-is-not-anonymous/RULE.md} +0 -0
  64. /package/presets/effect/basics/{gen-for-sequencing.md → gen-for-sequencing/RULE.md} +0 -0
  65. /package/presets/effect/config/{tests-provide-values-directly.md → tests-provide-values-directly/RULE.md} +0 -0
  66. /package/presets/effect/data/{brand-meaningful-primitives.md → brand-meaningful-primitives/RULE.md} +0 -0
  67. /package/presets/effect/data/{variants-are-tagged-unions.md → variants-are-tagged-unions/RULE.md} +0 -0
  68. /package/presets/effect/errors/{catch-defects-at-boundaries-only.md → catch-defects-at-boundaries-only/RULE.md} +0 -0
  69. /package/presets/effect/errors/{defects-for-bugs.md → defects-for-bugs/RULE.md} +0 -0
  70. /package/presets/effect/errors/{wrap-external-errors.md → wrap-external-errors/RULE.md} +0 -0
  71. /package/presets/effect/services/{dependencies-through-layers.md → dependencies-through-layers/RULE.md} +0 -0
  72. /package/presets/effect/services/{fresh-layer-per-test.md → fresh-layer-per-test/RULE.md} +0 -0
  73. /package/presets/effect/services/{memoize-parameterized-layers.md → memoize-parameterized-layers/RULE.md} +0 -0
  74. /package/presets/effect/services/{no-mutable-state.md → no-mutable-state/RULE.md} +0 -0
  75. /package/presets/effect/services/{operations-have-no-requirements.md → operations-have-no-requirements/RULE.md} +0 -0
  76. /package/presets/effect/services/{test-layers-are-in-memory.md → test-layers-are-in-memory/RULE.md} +0 -0
  77. /package/presets/effect/testing/{test-clock-for-time.md → test-clock-for-time/RULE.md} +0 -0
  78. /package/presets/react/effects/{clean-up-what-they-start.md → clean-up-what-they-start/RULE.md} +0 -0
  79. /package/presets/react/effects/{event-logic-in-handlers.md → event-logic-in-handlers/RULE.md} +0 -0
  80. /package/presets/react/effects/{external-stores-use-sync-external-store.md → external-stores-use-sync-external-store/RULE.md} +0 -0
  81. /package/presets/react/effects/{fetches-ignore-stale-responses.md → fetches-ignore-stale-responses/RULE.md} +0 -0
  82. /package/presets/react/effects/{notify-parents-in-handlers.md → notify-parents-in-handlers/RULE.md} +0 -0
  83. /package/presets/react/server/{no-private-data-to-client.md → no-private-data-to-client/RULE.md} +0 -0
  84. /package/presets/react/server/{server-functions-authorize-and-validate.md → server-functions-authorize-and-validate/RULE.md} +0 -0
  85. /package/presets/react/state/{no-contradictory-state.md → no-contradictory-state/RULE.md} +0 -0
  86. /package/presets/react/state/{no-props-copied-into-state.md → no-props-copied-into-state/RULE.md} +0 -0
  87. /package/presets/react/state/{store-ids-not-copies.md → store-ids-not-copies/RULE.md} +0 -0
  88. /package/presets/security/{constant-time-secret-compare.md → constant-time-secret-compare/RULE.md} +0 -0
  89. /package/presets/security/{no-secrets-in-output.md → no-secrets-in-output/RULE.md} +0 -0
  90. /package/presets/security/{no-untrusted-input-in-commands-or-paths.md → no-untrusted-input-in-commands-or-paths/RULE.md} +0 -0
  91. /package/presets/security/{parameterized-queries.md → parameterized-queries/RULE.md} +0 -0
  92. /package/presets/typescript/async/{concurrent-independent-work.md → concurrent-independent-work/RULE.md} +0 -0
  93. /package/presets/typescript/async/{network-calls-have-timeouts.md → network-calls-have-timeouts/RULE.md} +0 -0
  94. /package/presets/typescript/design/{no-argument-mutation.md → no-argument-mutation/RULE.md} +0 -0
  95. /package/presets/typescript/errors/{no-swallowed-errors.md → no-swallowed-errors/RULE.md} +0 -0
  96. /package/presets/typescript/errors/{release-on-every-path.md → release-on-every-path/RULE.md} +0 -0
  97. /package/presets/typescript/testing/{assert-outcomes.md → assert-outcomes/RULE.md} +0 -0
  98. /package/presets/typescript/testing/{independent-tests.md → independent-tests/RULE.md} +0 -0
  99. /package/presets/typescript/types/{assertions-only-when-proven.md → assertions-only-when-proven/RULE.md} +0 -0
  100. /package/presets/typescript/types/{derive-dont-restate.md → derive-dont-restate/RULE.md} +0 -0
  101. /package/presets/typescript/types/{unrepresentable-invalid-states.md → unrepresentable-invalid-states/RULE.md} +0 -0
  102. /package/presets/typescript/types/{validate-external-data.md → validate-external-data/RULE.md} +0 -0
@@ -1,167 +1,255 @@
1
1
  ---
2
2
  name: adhere
3
- description: Gather a repository's non-deterministic coding conventions into adhere rules (Markdown files with a description and code that must, or must never, be written, judged by Jev), configure adhere, run it, and calibrate thresholds. Use when setting up adhere in a repo, when the user asks to turn conventions, AGENTS.md guidance, ADRs, or review feedback into lint rules a normal linter cannot express, or when tuning adhere findings.
3
+ description: Reference for adhere, the linter for coding conventions a normal linter cannot check, judged by TypeSafe AI's Jev. Covers its commands, the Markdown rule format and how to word a rule, the config, presets, shared rules, comments and suppressions, the cache, reading a report, and tuning a noisy rule. Use when writing or editing adhere rules, configuring adhere, reading its findings, or answering questions about it. To set adhere up in a repo, use adhere-setup; to fix findings, adhere-fix.
4
4
  ---
5
5
 
6
- # Gather adhere rules from a repo
6
+ # adhere
7
7
 
8
8
  adhere is a linter for rules a normal linter cannot check. Each rule is one
9
9
  sentence in RFC 2119's words plus code that must be written and code that must
10
- never be; Jev judges every source file against it and returns a probability. Your job is to find the conventions a repo already
11
- has, write the ones that need judgment as rule files, and tune until the
12
- report is trustworthy.
10
+ never be. Jev, TypeSafe AI's model, reads every source file against every rule
11
+ and answers with the probability that the file breaks it; adhere reports a
12
+ rule above its threshold, 0.8 by default, in the section of the file Jev
13
+ points to. A finding is a judgment, not a proof.
13
14
 
14
- ## 1. Survey where conventions live
15
+ Two other skills build on this one: adhere-setup walks a repo through its
16
+ first rules, first lint, and CI, and adhere-fix verifies and fixes findings.
17
+ `adhere skill setup` and `adhere skill fix` print them.
15
18
 
16
- Read, in this order, and note every "we always" or "never" statement:
19
+ ## Commands
17
20
 
18
- - `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `README.md` convention sections.
19
- - `docs/`, ADRs, design notes, `CONTEXT.md`.
20
- - Lint and type configuration (`oxlint`, `eslint`, `biome`, `tsconfig`). These
21
- tell you what is already enforced deterministically, so you can exclude it.
22
- - Recent review feedback: `gh pr list --state merged --limit 20`, then
23
- `gh pr view <n> --comments` on a few. Repeated review comments are unwritten
24
- rules.
25
- - The code. Find the module others copy from: the canonical service, the
26
- canonical error type, the canonical test. Those files are your references.
27
-
28
- ## 2. Keep only what needs judgment
29
-
30
- For each candidate ask: could a regex, an import check, or the type checker
31
- flag every violation? If yes, it belongs in the existing linter, not adhere.
32
- Drop it or file it as a lint request.
33
-
34
- Keep rules about intent, shape, and placement:
35
-
36
- - "Sequential effectful steps use `Effect.gen`, not nested `flatMap`."
37
- - "A call over the network carries a timeout and a retry schedule."
38
- - "Business logic depends on a config service, not on `Config.*` reads."
39
- - "Domain errors are `Schema.TaggedError` classes, not thrown `Error`."
21
+ ```sh
22
+ adhere init [--force] # .adhere/config.ts, two example rules, and the dev dependency
23
+ adhere init --shared org/repo # a repo of rules other repos install, with CI and an alchemy stack
24
+ adhere login # save a TypeSafe AI API key; TYPESAFE_API_KEY takes precedence
25
+ adhere validate # rule wording, rules a linter could check, contradictions
26
+ adhere lint # judge the working directory; exit 1 on an error finding
27
+ adhere lint --limit 0 # show the plan and its cost, judge nothing
28
+ adhere lint --yes # send without asking, as in CI
29
+ adhere lint --preset effect # add a built-in rule set; the config becomes optional
30
+ adhere lint --filter 'src/**' # read only matching files; repeat, and ! to leave out
31
+ adhere lint --threshold 0.9 # report at another threshold from the cache; judges nothing again
32
+ adhere lint --deny-warnings # fail on warnings too
33
+ adhere list org/repo # the rules in a GitHub repo's .adhere/rules/
34
+ adhere install org/repo[/topic[/rule]][#ref] # copy them into .adhere/rules/org/repo/
35
+ adhere skill [docs|setup|fix] # print a skill
36
+ ```
40
37
 
41
- Skip rules a preset already covers. Run `adhere lint --help` and read
42
- `presets/effect/` in the adhere repo before writing Effect rules; extend with
43
- `presets: ["effect"]` and add only what is specific to this repo.
38
+ `adhere <command> --help` lists every flag.
44
39
 
45
- ## 3. Write each rule as a file
40
+ ## Rule files
46
41
 
47
- Default location `.adhere/<topic>/<slug>.md`, next to the config and the
48
- cache. Use `docs/adhere/<topic>/<slug>.md` with `rules: "./docs/adhere"` in the
49
- config only when the user wants rules kept with the repo's docs; rules read
50
- that way apply project-wide, and nested `.adhere/` directories are not read.
51
- The path without `.md` is the rule id.
42
+ A repo's rules live in `.adhere/rules/`, a directory per rule holding a
43
+ `RULE.md`, or a `RULE.ts` (below). The directory's path is the rule id:
44
+ `.adhere/rules/data/brand-ports/RULE.md` is `data/brand-ports`. Other files in
45
+ a rule's directory are not rules. A `*.md` rule elsewhere in `.adhere/`, as
46
+ rules were once written, refuses the run with where it goes to keep its id.
47
+ A `.adhere/rules/` in a subdirectory holds rules for that
48
+ subtree only, and the most specific rule with an id wins. `cache/` and the
49
+ config in `.adhere/` are not rules.
52
50
 
53
51
  ````md
54
52
  ---
55
- description: One sentence saying what code must be, then what it must never be.
56
- threshold: 0.8
53
+ description: A port must be a branded, range-checked integer, never a bare number.
57
54
  ---
58
55
 
59
- Optional prose for readers on GitHub. adhere ignores it.
56
+ Prose here renders on GitHub and is ignored.
60
57
 
61
58
  ## Must
62
59
 
63
60
  ```ts
64
- // Correct code lifted from this repo, trimmed to the pattern.
61
+ const Port = Schema.Int.pipe(
62
+ Schema.check(Schema.isBetween({ minimum: 1, maximum: 65535 })),
63
+ Schema.brand("Port"),
64
+ );
65
65
  ```
66
66
 
67
67
  ## Never
68
68
 
69
69
  ```ts
70
- // Optional: the incorrect form this rule catches, as it appeared in the repo.
70
+ const port: number = Number(process.env.PORT);
71
71
  ```
72
72
  ````
73
73
 
74
- Rules for the rule:
75
-
76
- - `description` is one sentence, specific, in the same words as the headings:
77
- what code must be, and what it must never be. Jev reads the description
78
- with the code, and "must" in both is one demand; vague words ("properly",
79
- "correctly") give it nothing to judge.
80
- - A heading names the code under it only when it is the word alone: `## Never`,
81
- not `## Never do this`. The next heading at its level or higher ends it.
82
- - The `must` block is real code from the repo, not invented, and only correct
83
- code: Jev compares files to it, so incorrect code there teaches the wrong
84
- thing. Incorrect code goes under `## Never`, which Jev reads as what a
85
- violation looks like. Add one whenever violations have a recognizable
86
- shape, ideally one found in the repo's history: over the effect preset, a
87
- `never` block raised how well Jev told violations from compliant code under
88
- every wording tried. A rule with no single correct form can be only a
89
- `never` block.
90
- - A convention that is a guideline rather than a requirement says "should" in
91
- its description and puts its code under `## Should` and `## Should not`. A
92
- rule is one or the other; it cannot mix the two.
93
- - One pattern per file. Two patterns in one `must` block blur the probability.
94
- - Jev reads code without its comments, except comments that say `@adhere`. A
95
- rule about comments, such as doc comments on exports, needs
96
- `includeComments: true` in the config.
97
- - Rules skip tests: `.test` and `.spec` files, such as `a.test.ts` and
98
- `Button.spec.tsx`, and files under `test/`, `tests/`, `__tests__/`, or
99
- `fixtures/`. A rule about tests says `tests: only` in its front matter, and
100
- one that holds in tests as well `tests: include`.
101
- - A nit, or a rule that tends to flag code wrongly, says `level: warning` in
102
- its front matter: its findings show in amber and do not fail CI.
103
- - `threshold` is optional. Start without it; set it in step 5 if needed.
104
-
105
- ## 4. Configure
106
-
107
- Put the config at `.adhere/config.ts`. `adhere.config.ts` and
108
- `.adhere.config.ts` also load; keep one. A config is optional when `.adhere/`
109
- exists or a preset is passed on the command line. `adhere init` scaffolds it
110
- with `defineConfig`, adds `@drkmttr/adhere` as a dev dependency, and writes
111
- `.adhere/tsconfig.json` so the editor resolves its types; the native binary
112
- supplies the import either way:
74
+ How to word a rule, from adhere's evals:
75
+
76
+ - The description is one specific sentence in the same words as the headings:
77
+ what code must be, then what it must never be. Vague words such as
78
+ "properly" give Jev nothing to judge.
79
+ - Give one example of each kind: one `## Must` block and one `## Never` block.
80
+ Three of each did no better, and several of one kind alone did worse. A rule
81
+ with no single correct form can have only a `never` block.
82
+ - The `must` block is real, correct code from the repo, trimmed to the
83
+ pattern; Jev compares files to it. Incorrect code goes under `## Never`,
84
+ ideally a violation found in the repo or its history.
85
+ - A guideline rather than a requirement says "should" and "should not", in
86
+ the description and as `## Should` and `## Should not`. A rule is one or the
87
+ other.
88
+ - One pattern per rule. Two in one block blur the probability.
89
+ - A heading names its code only when it is the word alone: `## Never`, not
90
+ `## Never do this`. A fence tagged `ts never` names its code itself.
91
+
92
+ Front matter besides `description`, all optional:
93
+
94
+ - `threshold: 0.9` sets the rule's own cutoff.
95
+ - `level: warning` reports its findings in amber without failing the run, for
96
+ a nit or a rule that errs toward false findings.
97
+ - `tests: only` for a rule about tests, `tests: include` for one that holds in
98
+ tests too. Otherwise rules skip `.test` and `.spec` files and files under
99
+ `test/`, `tests/`, `__tests__/`, and `fixtures/`.
100
+ - `appliesTo: ["a declared union type or schema"]` and
101
+ `excludeIf: ["a type that mirrors a third-party format"]`, JSON arrays on
102
+ one line, scope a rule without widening its description: a finding stands
103
+ only when Jev says the code that breaks the rule is as every `appliesTo`
104
+ describes and as no `excludeIf` does.
105
+
106
+ A rule's directory can hold a `RULE.ts` instead, which default-exports
107
+ `defineRule({...})` from `@drkmttr/adhere`, with the fields a config's inline
108
+ rule takes:
109
+ `description`, `must`, `never`, and the rest. Its `appendState(state, file, Bun)`
110
+ runs right before each request for the rule, and what it returns is spread
111
+ over the request's state, `{ code: { "1": "…", "2": "…" } }`, the file's
112
+ sections as Jev reads them. Use it to give Jev what the file cannot show, such
113
+ as a schema another file holds, and name the key it adds in the description.
114
+ Nothing it returns is checked, so it can break its own rule. The rule gets
115
+ requests of its own, one more per file judged, and its judgments are not
116
+ redone when what the hook reads changes, only when the file or the rule,
117
+ hook included, does. Helpers it imports go beside it in its directory.
118
+
119
+ A rule a regex, an import check, or the type checker could flag every time
120
+ belongs in that tool, not adhere. `adhere validate` reports rules that look
121
+ like that.
122
+
123
+ ## Config
124
+
125
+ A config is optional when `.adhere/rules/` holds rules or `--preset` is given. It
126
+ sits at `.adhere/config.ts`, `adhere.config.ts`, or `.adhere.config.ts`; keep
127
+ one.
113
128
 
114
129
  ```ts
115
130
  import { defineConfig } from "@drkmttr/adhere";
116
131
 
117
132
  export default defineConfig({
118
- presets: ["effect"],
119
- threshold: 0.75,
120
- exclude: ["**/generated/**"],
133
+ presets: ["typescript", "security"], // built-in rule sets, or topics such as "effect/basics"
134
+ threshold: 0.8, // default 0.8
135
+ sufficiencyThreshold: 0.6, // below it, a finding warns the file may not show enough
136
+ exclude: ["**/generated/**"], // files no rule judges
137
+ includeComments: false, // true only for rules about comments
138
+ overrides: {
139
+ "effect/basics/instrument-with-pipe": "off",
140
+ "alchemy/providers/idempotent-delete": { level: "warning", threshold: 0.9 },
141
+ },
121
142
  });
122
143
  ```
123
144
 
124
- To quiet a preset rule that does not suit the repo, override it rather than
125
- copy it: `overrides: { "alchemy/providers/idempotent-delete": "warning" }`, or
126
- `"off"`, or `{ threshold: 0.9 }`. The id is the one the report shows.
145
+ `overrides` changes any rule by the id the report shows, a preset's with the
146
+ preset first, without copying it; an id no rule has refuses the run. `rules`
147
+ in the config, inline or as a directory of rule directories, replaces
148
+ `.adhere/rules/` entirely. Precedence, highest first: `--threshold`, the config, presets
149
+ in order, the default; a rule's own `threshold` beats those, and `overrides`
150
+ beats the rule.
151
+
152
+ Node packages cannot be imported from the config, except `@drkmttr/adhere`,
153
+ which the executable supplies.
154
+
155
+ ## Presets
156
+
157
+ `typescript` (any TypeScript project), `react`, `security`, `effect`, and
158
+ `alchemy` (code deployed with alchemy.run). Each divides into topics, its
159
+ subdirectories, and a topic is a preset of its own. Their rules are in the
160
+ adhere package under `presets/`, and at
161
+ https://github.com/darkmatter/adhere/tree/main/presets. A preset leaves out
162
+ what that ecosystem's linters check exactly. Quiet a preset rule with
163
+ `overrides` rather than copying it.
164
+
165
+ ## Shared rules
166
+
167
+ An organization keeps rules in one repo's `.adhere/rules/`, scaffolded by
168
+ `adhere init --shared org/repo`, and other repos copy them in with
169
+ `adhere install org/repo`, or one topic or rule with
170
+ `adhere install org/repo/<topic>[/<rule>]`. Each rule's whole directory lands
171
+ in `.adhere/rules/org/repo/`, so `data/brand-ports` becomes the rule
172
+ `org/repo/data/brand-ports`, and a copied `RULE.ts` runs whenever lint does.
173
+ They are the repo's own rules from then on; a second install skips existing
174
+ rules unless `--force`, which replaces their directories, local edits and all. The source is cloned with git,
175
+ so a private repo needs git's credentials for GitHub.
176
+
177
+ ## Comments and suppressions
178
+
179
+ Jev never sees comments: adhere blanks them before judging, since comments
180
+ claiming code is safe hid real violations in the evals. A comment containing
181
+ `@adhere` is a note Jev does read: use it for a checked fact the code relies
182
+ on that the file cannot show, with how you know.
183
+
184
+ ```ts
185
+ // @adhere DeleteActivity succeeds on a missing activity; probed 2026-09-25.
186
+ ```
187
+
188
+ A finding Jev got wrong is suppressed in the code, with the reason:
127
189
 
128
- `exclude` lists globs, from the working directory, of files no rule judges.
129
- Exclude generated and vendored code the skipped directories do not already
130
- cover, since findings there are noise no one will fix.
190
+ ```ts
191
+ // adhere-ignore alchemy/providers/idempotent-delete -- DeleteActivity succeeds on a missing activity
192
+ ```
131
193
 
132
- Commit `.adhere/cache/` with the code, so the team and CI reuse its judgments
133
- instead of paying for them again. Uncached files need a TypeSafe AI API
134
- key: one saved by `adhere login`, or `TYPESAFE_API_KEY`, which takes
135
- precedence; in this org it comes from SOPS via `just shell`.
194
+ On its own line it covers the next statement; at the end of a line, the
195
+ statement starting there. `adhere-ignore-file <rule> -- <reason>` covers a
196
+ file. Name several rules with commas.
136
197
 
137
- ## 5. Run and calibrate
198
+ ## Cache and API key
138
199
 
139
- ```sh
140
- adhere validate # decodes the config and rules; reports their wording, rules a linter could check, and contradictions
141
- adhere lint # or: adhere lint --preset effect
142
- adhere lint --threshold 0.9 # re-reads the cache, sends nothing
200
+ Judgments are cached in `.adhere/cache/`, keyed by each file's content without
201
+ comments and by the rule's text. Commit it: every judgment is a paid request,
202
+ and a committed cache gives the team and CI the same findings without paying
203
+ again. A changed file re-judges every rule for it; an edited rule re-judges
204
+ only that rule; a threshold change re-judges nothing. A run where everything
205
+ is cached needs no key or network. Mark it generated to keep it out of diffs:
206
+
207
+ ```text
208
+ .adhere/cache/** linguist-generated -diff
143
209
  ```
144
210
 
145
- Then, per rule, read the findings and decide:
211
+ The key comes from `TYPESAFE_API_KEY` or from `adhere login`. lint prints a
212
+ plan with the request count and estimated cost before sending, and asks at a
213
+ terminal.
146
214
 
147
- - Many hits at 0.80 to 0.90, mostly not violations: the description is too
148
- broad. Narrow it (say what is out of scope) or raise that rule's
149
- `threshold`. Prefer narrowing; a threshold hides, a sentence explains.
150
- - Hits on code that follows the pattern through a different API: the `must`
151
- block is too specific. Use the repo's most general correct example.
152
- - Zero hits: plant a deliberate violation in a scratch file, run, confirm the
153
- rule fires, remove the file. A rule that cannot fire is not a rule.
154
- - Hits at 0.9 and above: read them first. They are usually real.
215
+ ## Reading a report
216
+
217
+ ```text
218
+ × data/brand-ports: A port must be a branded, range-checked integer, never a bare number.
219
+ confidence 0.93 · context 0.88
220
+ ╭─[src/server.ts:6:3]
221
+ 6 │ const port: number = Number(process.env.PORT ?? 3000);
222
+ · ──────────────────────────────────────────────────────
223
+ hint: const Port = Schema.Int.pipe(...)
224
+ ```
155
225
 
156
- To verify and fix the findings themselves, one at a time, use the adhere-fix
157
- skill, which `adhere skill fix` prints.
226
+ The header is the rule id and the description, with two scores on the next
227
+ line; `×` is an error, `⚠` a warning. `confidence` is Jev's probability that
228
+ the file breaks the rule. On a terminal it is amber near its rule's
229
+ threshold, within a quarter of the room above it (0.80 to 0.84 at a threshold
230
+ of 0.80), and green above that: a rule whose findings are mostly amber turns
231
+ on where its threshold sits. `context` is its probability that the file shows
232
+ enough to decide, colored the same way around the sufficiency threshold, 0.6
233
+ by default: amber from 0.50 to 0.69, red below. Below 0.6 the finding also
234
+ carries `warning: this file may not show enough to check this rule`. It turns
235
+ on something outside the file, and was usually false in the evals: check that
236
+ before acting on it. The underline is the line Jev names.
158
237
 
159
- Threshold changes never re-judge; only edited rules do (per rule) and edited
160
- files do (per file). Iterate on wording freely, it costs one request per file
161
- per changed rule.
238
+ ## Tuning a rule
162
239
 
163
- ## 6. Report
240
+ Per rule, read its findings and decide:
164
241
 
165
- Give the user: the rule ids written and where, the config path, the finding
166
- count at the chosen threshold and at 0.9, and the rules you dropped as
167
- deterministic with where they belong instead.
242
+ - Many hits at 0.80 to 0.90, mostly not violations: the description is too
243
+ broad. Narrow it, or scope it with `appliesTo` or `excludeIf`, before
244
+ raising its `threshold`: a threshold hides, a sentence explains.
245
+ - Hits on code that follows the pattern through another API: the `must`
246
+ block is too specific. Use the repo's most general correct example.
247
+ - Right less than half the time and not fixable by wording: `level: warning`,
248
+ or `off`.
249
+ - Zero hits: plant a violation in a scratch file, run lint on it with
250
+ `--filter`, confirm the rule fires, and delete the file. A rule that cannot
251
+ fire is not a rule.
252
+ - Hits at 0.9 and above are usually real. Read them first.
253
+
254
+ Editing a rule re-judges only that rule, one request per file, so iterate on
255
+ wording freely.
@@ -32,14 +32,15 @@ rules that changed.
32
32
  Each finding shows:
33
33
 
34
34
  - the rule id, a preset's with its preset first, as in
35
- `alchemy/providers/idempotent-delete`, Jev's probability, and the rule's
36
- description;
35
+ `alchemy/providers/idempotent-delete`, and the rule's description, and on
36
+ the next line `confidence`, Jev's probability that the file breaks the
37
+ rule, and `context`, its probability that the file shows enough to decide;
37
38
  - the section of the file Jev points at, a run of whole statements of about 30
38
39
  lines, with the lines it names underlined, or marked with a bar when they
39
40
  are several: usually where the violation starts, which may run on past them;
40
- - sometimes `warning: this file may not show enough to check this rule`: the
41
- rule may turn on another file, a library, or a service. Most of these are
42
- false; read what the code relies on before fixing it. Its `help:` suggests
41
+ - when `context` is low, `warning: this file may not show enough to check
42
+ this rule`: the rule may turn on another file, a library, or a service. Most
43
+ of these are false; read what the code relies on before fixing it. Its `help:` suggests
43
44
  an `@adhere` note, which follows the same rule as a suppression (see below);
44
45
  - `hint:`, the code the rule wants, or `never:` for a rule that only shows code
45
46
  to avoid.
@@ -53,9 +54,9 @@ are wrong more often.
53
54
  The report gives the rule's description and one example. Read the whole rule:
54
55
  the code under `## Must` and `## Never`, and anything it puts out of scope.
55
56
 
56
- - A project rule is `.adhere/<id>.md`, or under the directory the config's
57
- `rules` names.
58
- - A preset rule is `presets/<preset>/<id>.md` in the adhere package, in
57
+ - A project rule is `.adhere/rules/<id>/RULE.md`, or `RULE.ts`, or under the
58
+ directory the config's `rules` names.
59
+ - A preset rule is `presets/<preset>/<id>/RULE.md` in the adhere package, in
59
60
  `node_modules/@drkmttr/adhere/presets/`, or at
60
61
  https://github.com/darkmatter/adhere/tree/main/presets.
61
62