@drkmttr/adhere 0.11.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.
- package/README.md +184 -37
- package/package.json +9 -9
- package/presets/effect/basics/{instrument-with-pipe.md → instrument-with-pipe/RULE.md} +1 -1
- package/presets/effect/config/{business-logic-depends-on-config-service.md → business-logic-depends-on-config-service/RULE.md} +3 -3
- package/presets/effect/config/{secrets-are-redacted.md → secrets-are-redacted/RULE.md} +3 -3
- package/presets/effect/config/{validate-with-schema.md → validate-with-schema/RULE.md} +4 -4
- package/skills/adhere/SKILL.md +206 -118
- package/skills/adhere-fix/SKILL.md +9 -8
- package/skills/adhere-setup/SKILL.md +244 -0
- package/src/cli.ts +114 -25
- package/src/config.ts +50 -3
- package/src/index.ts +2 -0
- package/src/init.ts +193 -27
- package/src/install.ts +158 -0
- package/src/main.ts +1 -1
- package/src/rules.ts +135 -56
- package/src/services/AdhereConfig.ts +2 -2
- package/src/services/Credentials.ts +4 -4
- package/src/services/Jev.http.ts +76 -10
- package/src/services/Jev.ts +39 -10
- package/src/workflows/audit.ts +23 -12
- package/src/workflows/format.ts +172 -35
- /package/presets/alchemy/apis/{match-remote-errors-by-tag.md → match-remote-errors-by-tag/RULE.md} +0 -0
- /package/presets/alchemy/apis/{schemas-at-trust-boundaries.md → schemas-at-trust-boundaries/RULE.md} +0 -0
- /package/presets/alchemy/data/{commit-migrations-then-deploy.md → commit-migrations-then-deploy/RULE.md} +0 -0
- /package/presets/alchemy/data/{hyperdrive-direct-origin.md → hyperdrive-direct-origin/RULE.md} +0 -0
- /package/presets/alchemy/data/{migrations-use-direct-url.md → migrations-use-direct-url/RULE.md} +0 -0
- /package/presets/alchemy/data/{never-disable-tls-verification.md → never-disable-tls-verification/RULE.md} +0 -0
- /package/presets/alchemy/data/{private-endpoint-at-runtime.md → private-endpoint-at-runtime/RULE.md} +0 -0
- /package/presets/alchemy/data/{single-migration-owner.md → single-migration-owner/RULE.md} +0 -0
- /package/presets/alchemy/durable/{idempotent-event-handlers.md → idempotent-event-handlers/RULE.md} +0 -0
- /package/presets/alchemy/durable/{io-inside-steps.md → io-inside-steps/RULE.md} +0 -0
- /package/presets/alchemy/durable/{no-self-triggering-bucket-writes.md → no-self-triggering-bucket-writes/RULE.md} +0 -0
- /package/presets/alchemy/durable/{stable-step-names.md → stable-step-names/RULE.md} +0 -0
- /package/presets/alchemy/durable/{webhook-state-is-truth.md → webhook-state-is-truth/RULE.md} +0 -0
- /package/presets/alchemy/providers/{diff-guards-unresolved-props.md → diff-guards-unresolved-props/RULE.md} +0 -0
- /package/presets/alchemy/providers/{idempotent-delete.md → idempotent-delete/RULE.md} +0 -0
- /package/presets/alchemy/providers/{lazy-layer-construction.md → lazy-layer-construction/RULE.md} +0 -0
- /package/presets/alchemy/providers/{reconcile-from-observed-state.md → reconcile-from-observed-state/RULE.md} +0 -0
- /package/presets/alchemy/resources/{anchor-paths-with-import-meta.md → anchor-paths-with-import-meta/RULE.md} +0 -0
- /package/presets/alchemy/resources/{idempotent-actions-and-seeds.md → idempotent-actions-and-seeds/RULE.md} +0 -0
- /package/presets/alchemy/resources/{previews-reference-shared-resources.md → previews-reference-shared-resources/RULE.md} +0 -0
- /package/presets/alchemy/resources/{protect-irreplaceable-data.md → protect-irreplaceable-data/RULE.md} +0 -0
- /package/presets/alchemy/resources/{remote-state-for-ci.md → remote-state-for-ci/RULE.md} +0 -0
- /package/presets/alchemy/resources/{stable-logical-ids.md → stable-logical-ids/RULE.md} +0 -0
- /package/presets/alchemy/resources/{stage-unique-physical-names.md → stage-unique-physical-names/RULE.md} +0 -0
- /package/presets/alchemy/runtime/{bind-tag-not-implementation.md → bind-tag-not-implementation/RULE.md} +0 -0
- /package/presets/alchemy/runtime/{deterministic-config-defaults.md → deterministic-config-defaults/RULE.md} +0 -0
- /package/presets/alchemy/runtime/{disposables-in-request-scope.md → disposables-in-request-scope/RULE.md} +0 -0
- /package/presets/alchemy/runtime/{lambda-no-post-response-work.md → lambda-no-post-response-work/RULE.md} +0 -0
- /package/presets/alchemy/runtime/{no-per-request-work-at-construction.md → no-per-request-work-at-construction/RULE.md} +0 -0
- /package/presets/alchemy/runtime/{resolve-config-and-bindings-at-construction.md → resolve-config-and-bindings-at-construction/RULE.md} +0 -0
- /package/presets/alchemy/secrets/{let-alchemy-mint-infra-secrets.md → let-alchemy-mint-infra-secrets/RULE.md} +0 -0
- /package/presets/alchemy/secrets/{none-in-browser-env.md → none-in-browser-env/RULE.md} +0 -0
- /package/presets/alchemy/secrets/{redacted-until-use.md → redacted-until-use/RULE.md} +0 -0
- /package/presets/alchemy/secrets/{unwrap-before-compare-or-send.md → unwrap-before-compare-or-send/RULE.md} +0 -0
- /package/presets/alchemy/security/{authorize-public-endpoints.md → authorize-public-endpoints/RULE.md} +0 -0
- /package/presets/alchemy/security/{github-secret-not-variable.md → github-secret-not-variable/RULE.md} +0 -0
- /package/presets/alchemy/security/{identity-from-verified-session.md → identity-from-verified-session/RULE.md} +0 -0
- /package/presets/alchemy/security/{keep-auth-protections-on.md → keep-auth-protections-on/RULE.md} +0 -0
- /package/presets/alchemy/security/{least-privilege-bindings.md → least-privilege-bindings/RULE.md} +0 -0
- /package/presets/alchemy/security/{least-privilege-ci-credentials.md → least-privilege-ci-credentials/RULE.md} +0 -0
- /package/presets/alchemy/security/{lookup-failure-is-not-anonymous.md → lookup-failure-is-not-anonymous/RULE.md} +0 -0
- /package/presets/effect/basics/{gen-for-sequencing.md → gen-for-sequencing/RULE.md} +0 -0
- /package/presets/effect/config/{tests-provide-values-directly.md → tests-provide-values-directly/RULE.md} +0 -0
- /package/presets/effect/data/{brand-meaningful-primitives.md → brand-meaningful-primitives/RULE.md} +0 -0
- /package/presets/effect/data/{variants-are-tagged-unions.md → variants-are-tagged-unions/RULE.md} +0 -0
- /package/presets/effect/errors/{catch-defects-at-boundaries-only.md → catch-defects-at-boundaries-only/RULE.md} +0 -0
- /package/presets/effect/errors/{defects-for-bugs.md → defects-for-bugs/RULE.md} +0 -0
- /package/presets/effect/errors/{wrap-external-errors.md → wrap-external-errors/RULE.md} +0 -0
- /package/presets/effect/services/{dependencies-through-layers.md → dependencies-through-layers/RULE.md} +0 -0
- /package/presets/effect/services/{fresh-layer-per-test.md → fresh-layer-per-test/RULE.md} +0 -0
- /package/presets/effect/services/{memoize-parameterized-layers.md → memoize-parameterized-layers/RULE.md} +0 -0
- /package/presets/effect/services/{no-mutable-state.md → no-mutable-state/RULE.md} +0 -0
- /package/presets/effect/services/{operations-have-no-requirements.md → operations-have-no-requirements/RULE.md} +0 -0
- /package/presets/effect/services/{test-layers-are-in-memory.md → test-layers-are-in-memory/RULE.md} +0 -0
- /package/presets/effect/testing/{test-clock-for-time.md → test-clock-for-time/RULE.md} +0 -0
- /package/presets/react/effects/{clean-up-what-they-start.md → clean-up-what-they-start/RULE.md} +0 -0
- /package/presets/react/effects/{event-logic-in-handlers.md → event-logic-in-handlers/RULE.md} +0 -0
- /package/presets/react/effects/{external-stores-use-sync-external-store.md → external-stores-use-sync-external-store/RULE.md} +0 -0
- /package/presets/react/effects/{fetches-ignore-stale-responses.md → fetches-ignore-stale-responses/RULE.md} +0 -0
- /package/presets/react/effects/{notify-parents-in-handlers.md → notify-parents-in-handlers/RULE.md} +0 -0
- /package/presets/react/server/{no-private-data-to-client.md → no-private-data-to-client/RULE.md} +0 -0
- /package/presets/react/server/{server-functions-authorize-and-validate.md → server-functions-authorize-and-validate/RULE.md} +0 -0
- /package/presets/react/state/{no-contradictory-state.md → no-contradictory-state/RULE.md} +0 -0
- /package/presets/react/state/{no-props-copied-into-state.md → no-props-copied-into-state/RULE.md} +0 -0
- /package/presets/react/state/{store-ids-not-copies.md → store-ids-not-copies/RULE.md} +0 -0
- /package/presets/security/{constant-time-secret-compare.md → constant-time-secret-compare/RULE.md} +0 -0
- /package/presets/security/{no-secrets-in-output.md → no-secrets-in-output/RULE.md} +0 -0
- /package/presets/security/{no-untrusted-input-in-commands-or-paths.md → no-untrusted-input-in-commands-or-paths/RULE.md} +0 -0
- /package/presets/security/{parameterized-queries.md → parameterized-queries/RULE.md} +0 -0
- /package/presets/typescript/async/{concurrent-independent-work.md → concurrent-independent-work/RULE.md} +0 -0
- /package/presets/typescript/async/{network-calls-have-timeouts.md → network-calls-have-timeouts/RULE.md} +0 -0
- /package/presets/typescript/design/{no-argument-mutation.md → no-argument-mutation/RULE.md} +0 -0
- /package/presets/typescript/errors/{no-swallowed-errors.md → no-swallowed-errors/RULE.md} +0 -0
- /package/presets/typescript/errors/{release-on-every-path.md → release-on-every-path/RULE.md} +0 -0
- /package/presets/typescript/testing/{assert-outcomes.md → assert-outcomes/RULE.md} +0 -0
- /package/presets/typescript/testing/{independent-tests.md → independent-tests/RULE.md} +0 -0
- /package/presets/typescript/types/{assertions-only-when-proven.md → assertions-only-when-proven/RULE.md} +0 -0
- /package/presets/typescript/types/{derive-dont-restate.md → derive-dont-restate/RULE.md} +0 -0
- /package/presets/typescript/types/{unrepresentable-invalid-states.md → unrepresentable-invalid-states/RULE.md} +0 -0
- /package/presets/typescript/types/{validate-external-data.md → validate-external-data/RULE.md} +0 -0
package/skills/adhere/SKILL.md
CHANGED
|
@@ -1,167 +1,255 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: adhere
|
|
3
|
-
description:
|
|
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
|
-
#
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
## Commands
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
40
|
+
## Rule files
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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:
|
|
56
|
-
threshold: 0.8
|
|
53
|
+
description: A port must be a branded, range-checked integer, never a bare number.
|
|
57
54
|
---
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
Prose here renders on GitHub and is ignored.
|
|
60
57
|
|
|
61
58
|
## Must
|
|
62
59
|
|
|
63
60
|
```ts
|
|
64
|
-
|
|
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
|
-
|
|
70
|
+
const port: number = Number(process.env.PORT);
|
|
71
71
|
```
|
|
72
72
|
````
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
what code must be,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- The `must` block is real code from the repo,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
`
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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: ["
|
|
119
|
-
threshold: 0.
|
|
120
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
190
|
+
```ts
|
|
191
|
+
// adhere-ignore alchemy/providers/idempotent-delete -- DeleteActivity succeeds on a missing activity
|
|
192
|
+
```
|
|
131
193
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
##
|
|
198
|
+
## Cache and API key
|
|
138
199
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
240
|
+
Per rule, read its findings and decide:
|
|
164
241
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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`,
|
|
36
|
-
|
|
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
|
-
-
|
|
41
|
-
rule may turn on another file, a library, or a service. Most
|
|
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
|
|
57
|
-
`rules` names.
|
|
58
|
-
- A preset rule is `presets/<preset>/<id
|
|
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
|
|