@drkmttr/adhere 0.18.1 → 0.23.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 (43) hide show
  1. package/README.md +62 -1575
  2. package/package.json +18 -15
  3. package/presets/README.md +140 -0
  4. package/presets/alchemy/runtime/bind-tag-not-implementation/RULE.md +1 -0
  5. package/presets/effect/data/brand-meaningful-primitives/RULE.md +1 -0
  6. package/presets/effect/data/variants-are-tagged-unions/RULE.md +1 -0
  7. package/presets/typescript/types/derive-dont-restate/RULE.md +1 -0
  8. package/skills/adhere/SKILL.md +194 -29
  9. package/skills/adhere-fix/SKILL.md +1 -1
  10. package/skills/adhere-setup/SKILL.md +60 -26
  11. package/src/cli.ts +334 -36
  12. package/src/config.ts +173 -142
  13. package/src/create/agents.ts +149 -0
  14. package/src/create/ask.ts +363 -0
  15. package/src/create/examples.ts +94 -0
  16. package/src/create/look.ts +191 -0
  17. package/src/create/oxlint-trial.ts +464 -0
  18. package/src/create/oxlint.ts +1012 -0
  19. package/src/create/trial-review.ts +562 -0
  20. package/src/create/trial.ts +611 -0
  21. package/src/create/wizard.ts +466 -0
  22. package/src/index.ts +2 -0
  23. package/src/init.ts +171 -41
  24. package/src/install.ts +296 -94
  25. package/src/instructions.ts +98 -0
  26. package/src/markdown.ts +15 -11
  27. package/src/oxlint/catalog.json +7844 -0
  28. package/src/oxlint/catalog.ts +72 -0
  29. package/src/oxlint/shared.ts +140 -0
  30. package/src/readme-guide.ts +431 -0
  31. package/src/readme.ts +291 -0
  32. package/src/rules.ts +42 -45
  33. package/src/services/AdhereConfig.ts +67 -21
  34. package/src/services/AuditCache.ts +176 -169
  35. package/src/services/Credentials.ts +14 -8
  36. package/src/services/Jev.reads.test.ts +22 -0
  37. package/src/services/Jev.ts +35 -13
  38. package/src/services/SourceWalker.ts +12 -5
  39. package/src/wording.ts +4 -4
  40. package/src/workflows/audit.ts +85 -14
  41. package/src/workflows/format.ts +98 -1
  42. package/src/workspaces.test.ts +37 -1
  43. package/src/workspaces.ts +121 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drkmttr/adhere",
3
- "version": "0.18.1",
3
+ "version": "0.23.0",
4
4
  "private": false,
5
5
  "description": "Lints a repository against the rules in .adhere/, judged by Jev.",
6
6
  "license": "MIT",
@@ -34,36 +34,39 @@
34
34
  "typecheck": "tsc --noEmit && bun run lint",
35
35
  "build": "bun build --compile --minify --sourcemap --define ADHERE_BUILD=\"\\\"$(git describe --tags --dirty --always)\\\"\" src/main.ts --asset ./presets --outfile dist/adhere",
36
36
  "build:npm": "bun scripts/npm-packages.ts build",
37
+ "oxlint:catalog": "bun scripts/oxlint-catalog.ts",
38
+ "oxlint:catalog:check": "bun scripts/oxlint-catalog.ts --check",
37
39
  "release": "release-it",
38
40
  "prepare": "vp config"
39
41
  },
40
42
  "dependencies": {
41
- "effect": "4.0.0-rc.118"
43
+ "effect": "4.0.3",
44
+ "mdast-util-from-markdown": "^2.1.0"
42
45
  },
43
46
  "devDependencies": {
44
- "@effect/platform-bun": "4.0.0-rc.118",
47
+ "@effect/platform-bun": "4.0.3",
45
48
  "@types/bun": "^1.4.2",
46
- "@types/node": "^24.13.6",
49
+ "@types/node": "^26.0.0",
47
50
  "release-it": "^21.1.0",
48
- "typescript": "7.1.0-dev.20261009.1",
51
+ "typescript": "7.1.0-dev.20261012.1",
49
52
  "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
50
53
  "vite-plus": "1.1.0",
51
- "vitest": "5.0.3"
54
+ "vitest": "5.0.5"
52
55
  },
53
56
  "overrides": {
54
- "@effect/platform-node-shared": "4.0.0-rc.118",
57
+ "@effect/platform-node-shared": "4.0.3",
55
58
  "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
56
- "vitest": "5.0.3"
59
+ "vitest": "5.0.5"
57
60
  },
58
61
  "devEngines": {
59
62
  "packageManager": {
60
63
  "name": "bun",
61
- "version": "1.4.2",
64
+ "version": "1.4.3",
62
65
  "onFail": "download"
63
66
  },
64
67
  "runtime": {
65
68
  "name": "node",
66
- "version": "24.21.0",
69
+ "version": "26.11.1",
67
70
  "onFail": "download"
68
71
  }
69
72
  },
@@ -90,10 +93,10 @@
90
93
  "--force"
91
94
  ],
92
95
  "optionalDependencies": {
93
- "@drkmttr/adhere-darwin-arm64": "0.18.1",
94
- "@drkmttr/adhere-darwin-x64": "0.18.1",
95
- "@drkmttr/adhere-linux-arm64": "0.18.1",
96
- "@drkmttr/adhere-linux-x64": "0.18.1",
97
- "@drkmttr/adhere-win32-x64": "0.18.1"
96
+ "@drkmttr/adhere-darwin-arm64": "0.23.0",
97
+ "@drkmttr/adhere-darwin-x64": "0.23.0",
98
+ "@drkmttr/adhere-linux-arm64": "0.23.0",
99
+ "@drkmttr/adhere-linux-x64": "0.23.0",
100
+ "@drkmttr/adhere-win32-x64": "0.23.0"
98
101
  }
99
102
  }
@@ -0,0 +1,140 @@
1
+ # Presets
2
+
3
+ Every rule in adhere's built-in presets is listed below, with a one-line
4
+ summary. Each id links to the full rule, and is the id that reports,
5
+ `overrides`, and `adhere-ignore` comments use. [Presets](https://dm.sh/adhere/presets/)
6
+ says how to name a preset or one of its topics.
7
+
8
+ Some rules declare the context they need with `reads`, such as
9
+ `["symbols", "references"]` for a rule that compares declarations across
10
+ files. Enable `typeAware: true` or `--type-aware` for the compiler's
11
+ `symbols` and `references`. Without it, a bundled rule that declares either is
12
+ left out of the run, so a preset never judges with less context than its rules
13
+ declare. A project's own rule is different: it still runs, and lint warns that
14
+ it may be less accurate until a declared read is enabled.
15
+
16
+ ## typescript
17
+
18
+ - **Source.** The TypeScript Handbook and the Google TypeScript Style Guide.
19
+ - **Checked by a linter instead.** The preset has no rules for these, since
20
+ typescript-eslint and ESLint check them exactly: unhandled promises
21
+ (`no-floating-promises`), throwing non-errors (`only-throw-error`), `any`
22
+ (`no-explicit-any`), exhaustive switches (`switch-exhaustiveness-check`), a
23
+ lost `cause` (`preserve-caught-error`), and empty catch blocks (`no-empty`).
24
+
25
+ - [`typescript/async/concurrent-independent-work`](./typescript/async/concurrent-independent-work/RULE.md): Independent awaited calls run concurrently, not one after another.
26
+ - [`typescript/async/network-calls-have-timeouts`](./typescript/async/network-calls-have-timeouts/RULE.md): HTTP requests carry a timeout or an abort signal.
27
+ - [`typescript/design/no-argument-mutation`](./typescript/design/no-argument-mutation/RULE.md): Functions return changed copies instead of mutating arguments.
28
+ - [`typescript/errors/no-swallowed-errors`](./typescript/errors/no-swallowed-errors/RULE.md): Caught errors are handled, rethrown, or returned as failures.
29
+ - [`typescript/errors/release-on-every-path`](./typescript/errors/release-on-every-path/RULE.md): Acquired resources are released on every path, including errors.
30
+ - [`typescript/testing/assert-outcomes`](./typescript/testing/assert-outcomes/RULE.md): Tests assert results and effects, not only that code ran.
31
+ - [`typescript/testing/independent-tests`](./typescript/testing/independent-tests/RULE.md): Each test sets up its own state and relies on no other test.
32
+ - [`typescript/types/assertions-only-when-proven`](./typescript/types/assertions-only-when-proven/RULE.md): Type assertions claim only what surrounding code has proven.
33
+ - [`typescript/types/derive-dont-restate`](./typescript/types/derive-dont-restate/RULE.md): Types are derived from their source instead of restated by hand.
34
+ - [`typescript/types/unrepresentable-invalid-states`](./typescript/types/unrepresentable-invalid-states/RULE.md): Types make invalid field combinations unrepresentable.
35
+ - [`typescript/types/validate-external-data`](./typescript/types/validate-external-data/RULE.md): Data from outside is checked at runtime before use.
36
+
37
+ ## react
38
+
39
+ - **Source.** react.dev, mostly
40
+ [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
41
+ and
42
+ [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure).
43
+ - **Checked by a linter instead.** The preset has no rules for these, since
44
+ the React Compiler's lint rules check them, in
45
+ [eslint-plugin-react-hooks](https://react.dev/reference/eslint-plugin-react-hooks)'s
46
+ recommended set and in Oxlint: pure render (`purity`), props and state
47
+ never mutated (`immutability`), refs not read during render (`refs`), no
48
+ `setState` in render or synchronously in an effect (`set-state-in-render`,
49
+ `set-state-in-effect`), which covers state derived in an effect, and no
50
+ component defined inside another (`static-components`).
51
+
52
+ - [`react/effects/clean-up-what-they-start`](./react/effects/clean-up-what-they-start/RULE.md): Effects return cleanup that undoes what they started.
53
+ - [`react/effects/event-logic-in-handlers`](./react/effects/event-logic-in-handlers/RULE.md): Code responding to a user action runs in its event handler.
54
+ - [`react/effects/external-stores-use-sync-external-store`](./react/effects/external-stores-use-sync-external-store/RULE.md): External stores are read with `useSyncExternalStore`.
55
+ - [`react/effects/fetches-ignore-stale-responses`](./react/effects/fetches-ignore-stale-responses/RULE.md): Effects that fetch ignore or abort stale responses.
56
+ - [`react/effects/notify-parents-in-handlers`](./react/effects/notify-parents-in-handlers/RULE.md): Parents are notified in the handler that makes the change.
57
+ - [`react/server/no-private-data-to-client`](./react/server/no-private-data-to-client/RULE.md): Client Components receive only the fields they show.
58
+ - [`react/server/server-functions-authorize-and-validate`](./react/server/server-functions-authorize-and-validate/RULE.md): Server Functions authorize callers and validate arguments.
59
+ - [`react/state/no-contradictory-state`](./react/state/no-contradictory-state/RULE.md): One status value replaces booleans that can contradict.
60
+ - [`react/state/no-props-copied-into-state`](./react/state/no-props-copied-into-state/RULE.md): Props are read during render, not copied into state.
61
+ - [`react/state/store-ids-not-copies`](./react/state/store-ids-not-copies/RULE.md): State holds a selected item's id, not a copy of the item.
62
+
63
+ ## security
64
+
65
+ - **Source.** OWASP's cheat sheets.
66
+
67
+ - [`security/constant-time-secret-compare`](./security/constant-time-secret-compare/RULE.md): Received secrets are compared in constant time.
68
+ - [`security/no-secrets-in-output`](./security/no-secrets-in-output/RULE.md): Secrets stay out of logs, errors, and HTTP responses.
69
+ - [`security/no-untrusted-input-in-commands-or-paths`](./security/no-untrusted-input-in-commands-or-paths/RULE.md): Untrusted input stays out of command strings and unchecked paths.
70
+ - [`security/parameterized-queries`](./security/parameterized-queries/RULE.md): SQL values from outside are passed as bound parameters.
71
+
72
+ ## effect
73
+
74
+ - **Source.** The
75
+ [effect-solutions](https://github.com/kitlangton/effect-solutions) docs and
76
+ the [effect/platform](https://effect.website/docs/platform/introduction/)
77
+ docs.
78
+
79
+ - [`effect/basics/gen-for-sequencing`](./effect/basics/gen-for-sequencing/RULE.md): Sequential effectful steps use `Effect.gen` and `yield*`.
80
+ - [`effect/basics/instrument-with-pipe`](./effect/basics/instrument-with-pipe/RULE.md): Timeouts, retries, logging, and spans attach with `.pipe`.
81
+ - [`effect/config/business-logic-depends-on-config-service`](./effect/config/business-logic-depends-on-config-service/RULE.md): Business logic depends on a config service, not Config reads.
82
+ - [`effect/config/secrets-are-redacted`](./effect/config/secrets-are-redacted/RULE.md): Tokens, passwords, and keys are read with `Config.Redacted`.
83
+ - [`effect/config/tests-provide-values-directly`](./effect/config/tests-provide-values-directly/RULE.md): Tests supply config with `Layer.succeed` on the config service.
84
+ - [`effect/config/validate-with-schema`](./effect/config/validate-with-schema/RULE.md): Constrained config values are validated as they are read.
85
+ - [`effect/data/brand-meaningful-primitives`](./effect/data/brand-meaningful-primitives/RULE.md): Meaningful primitives in domain types are branded schemas.
86
+ - [`effect/data/variants-are-tagged-unions`](./effect/data/variants-are-tagged-unions/RULE.md): Domain variants are tagged schemas in a union.
87
+ - [`effect/errors/catch-defects-at-boundaries-only`](./effect/errors/catch-defects-at-boundaries-only/RULE.md): Defects are caught only at system boundaries.
88
+ - [`effect/errors/defects-for-bugs`](./effect/errors/defects-for-bugs/RULE.md): Bugs and invariant violations are defects, not typed errors.
89
+ - [`effect/errors/wrap-external-errors`](./effect/errors/wrap-external-errors/RULE.md): External errors are wrapped in tagged errors.
90
+ - [`effect/services/dependencies-through-layers`](./effect/services/dependencies-through-layers/RULE.md): Service dependencies are yielded inside the service's Layer.
91
+ - [`effect/services/fresh-layer-per-test`](./effect/services/fresh-layer-per-test/RULE.md): Each `it.effect` test provides its own layer.
92
+ - [`effect/services/memoize-parameterized-layers`](./effect/services/memoize-parameterized-layers/RULE.md): Parameterized layers are built once and stored in a constant.
93
+ - [`effect/services/no-mutable-state`](./effect/services/no-mutable-state/RULE.md): Service interfaces expose only readonly members.
94
+ - [`effect/testing/test-clock-for-time`](./effect/testing/test-clock-for-time/RULE.md): Tests that depend on time use `TestClock`.
95
+
96
+ ## alchemy
97
+
98
+ - **Source.** alchemy's docs and blog.
99
+
100
+ - [`alchemy/apis/match-remote-errors-by-tag`](./alchemy/apis/match-remote-errors-by-tag/RULE.md): Remote errors are matched by tag and data, not `instanceof`.
101
+ - [`alchemy/apis/schemas-at-trust-boundaries`](./alchemy/apis/schemas-at-trust-boundaries/RULE.md): Outside input is schema-validated at trust boundaries.
102
+ - [`alchemy/data/commit-migrations-then-deploy`](./alchemy/data/commit-migrations-then-deploy/RULE.md): Migrations are committed ahead of time and applied as committed.
103
+ - [`alchemy/data/hyperdrive-direct-origin`](./alchemy/data/hyperdrive-direct-origin/RULE.md): Hyperdrive points at the database's direct endpoint.
104
+ - [`alchemy/data/migrations-use-direct-url`](./alchemy/data/migrations-use-direct-url/RULE.md): Migration runners connect with the database's direct URL.
105
+ - [`alchemy/data/never-disable-tls-verification`](./alchemy/data/never-disable-tls-verification/RULE.md): Database connections verify the server's TLS certificate.
106
+ - [`alchemy/data/private-endpoint-at-runtime`](./alchemy/data/private-endpoint-at-runtime/RULE.md): Service code reaches its database through the private binding.
107
+ - [`alchemy/data/single-migration-owner`](./alchemy/data/single-migration-owner/RULE.md): Each database has exactly one migration owner.
108
+ - [`alchemy/durable/idempotent-event-handlers`](./alchemy/durable/idempotent-event-handlers/RULE.md): Handlers for at-least-once events are idempotent.
109
+ - [`alchemy/durable/io-inside-steps`](./alchemy/durable/io-inside-steps/RULE.md): Workflow I/O runs inside steps; replayed code stays deterministic.
110
+ - [`alchemy/durable/no-self-triggering-bucket-writes`](./alchemy/durable/no-self-triggering-bucket-writes/RULE.md): Bucket handlers filter out events from their own writes.
111
+ - [`alchemy/durable/stable-step-names`](./alchemy/durable/stable-step-names/RULE.md): Workflow step names stay stable across replays.
112
+ - [`alchemy/durable/webhook-state-is-truth`](./alchemy/durable/webhook-state-is-truth/RULE.md): Paid access depends on webhook-recorded state, not success URLs.
113
+ - [`alchemy/providers/diff-guards-unresolved-props`](./alchemy/providers/diff-guards-unresolved-props/RULE.md): Provider diffs guard unresolved props with `isResolved`.
114
+ - [`alchemy/providers/idempotent-delete`](./alchemy/providers/idempotent-delete/RULE.md): Provider delete treats an already-gone resource as success.
115
+ - [`alchemy/providers/lazy-layer-construction`](./alchemy/providers/lazy-layer-construction/RULE.md): Provider layers defer credentials and connections until use.
116
+ - [`alchemy/providers/reconcile-from-observed-state`](./alchemy/providers/reconcile-from-observed-state/RULE.md): Provider reconcile converges from the resource's live state.
117
+ - [`alchemy/resources/anchor-paths-with-import-meta`](./alchemy/resources/anchor-paths-with-import-meta/RULE.md): Path props of resources declared outside the run directory use `import.meta`.
118
+ - [`alchemy/resources/idempotent-actions-and-seeds`](./alchemy/resources/idempotent-actions-and-seeds/RULE.md): Actions and seed files are safe to run again.
119
+ - [`alchemy/resources/previews-reference-shared-resources`](./alchemy/resources/previews-reference-shared-resources/RULE.md): Preview stages reference shared resources and add cheap pieces.
120
+ - [`alchemy/resources/protect-irreplaceable-data`](./alchemy/resources/protect-irreplaceable-data/RULE.md): Buckets and databases with irreplaceable data are retained.
121
+ - [`alchemy/resources/remote-state-for-ci`](./alchemy/resources/remote-state-for-ci/RULE.md): CI and multi-machine stacks use a shared state store.
122
+ - [`alchemy/resources/stable-logical-ids`](./alchemy/resources/stable-logical-ids/RULE.md): Resource logical ids stay the same across deploys.
123
+ - [`alchemy/resources/stage-unique-physical-names`](./alchemy/resources/stage-unique-physical-names/RULE.md): Global physical names are derived per stage.
124
+ - [`alchemy/runtime/bind-tag-not-implementation`](./alchemy/runtime/bind-tag-not-implementation/RULE.md): Other runtimes bind the Tag; only the Stack provides the Layer.
125
+ - [`alchemy/runtime/deterministic-config-defaults`](./alchemy/runtime/deterministic-config-defaults/RULE.md): Config defaults and fallbacks are deterministic.
126
+ - [`alchemy/runtime/disposables-in-request-scope`](./alchemy/runtime/disposables-in-request-scope/RULE.md): Disposables are acquired inside handlers, within request scope.
127
+ - [`alchemy/runtime/lambda-no-post-response-work`](./alchemy/runtime/lambda-no-post-response-work/RULE.md): Lambda handlers write durable work before responding.
128
+ - [`alchemy/runtime/no-per-request-work-at-construction`](./alchemy/runtime/no-per-request-work-at-construction/RULE.md): Construction builds resources and handlers, not per-request work.
129
+ - [`alchemy/runtime/resolve-config-and-bindings-at-construction`](./alchemy/runtime/resolve-config-and-bindings-at-construction/RULE.md): Config and bindings are resolved at construction, not in handlers.
130
+ - [`alchemy/secrets/let-alchemy-mint-infra-secrets`](./alchemy/secrets/let-alchemy-mint-infra-secrets/RULE.md): Infrastructure tokens are minted once by alchemy, not hardcoded.
131
+ - [`alchemy/secrets/none-in-browser-env`](./alchemy/secrets/none-in-browser-env/RULE.md): Browser-inlined env values hold only public configuration.
132
+ - [`alchemy/secrets/redacted-until-use`](./alchemy/secrets/redacted-until-use/RULE.md): Secrets stay `Redacted` until the call that needs the raw string.
133
+ - [`alchemy/secrets/unwrap-before-compare-or-send`](./alchemy/secrets/unwrap-before-compare-or-send/RULE.md): `Redacted` values are unwrapped before compare, send, or serialize.
134
+ - [`alchemy/security/authorize-public-endpoints`](./alchemy/security/authorize-public-endpoints/RULE.md): Public handlers authenticate and authorize every caller.
135
+ - [`alchemy/security/github-secret-not-variable`](./alchemy/security/github-secret-not-variable/RULE.md): Sensitive CI values use `GitHub.Secret`, not `GitHub.Variable`.
136
+ - [`alchemy/security/identity-from-verified-session`](./alchemy/security/identity-from-verified-session/RULE.md): The acting user comes from the verified session or token.
137
+ - [`alchemy/security/keep-auth-protections-on`](./alchemy/security/keep-auth-protections-on/RULE.md): Auth checks and protections stay enabled.
138
+ - [`alchemy/security/least-privilege-bindings`](./alchemy/security/least-privilege-bindings/RULE.md): Runtimes bind the narrowest capability their calls need.
139
+ - [`alchemy/security/least-privilege-ci-credentials`](./alchemy/security/least-privilege-ci-credentials/RULE.md): CI credentials are scoped to what the stack deploys.
140
+ - [`alchemy/security/lookup-failure-is-not-anonymous`](./alchemy/security/lookup-failure-is-not-anonymous/RULE.md): A failed session lookup is an error, not an anonymous user.
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  description: When a runtime is split into a Tag class and a make Layer, other runtimes must import and bind only the Tag, and only the Stack provides the Layer; another Worker or Function must never import or provide that implementation Layer.
3
+ reads: ["symbols", "references"]
3
4
  ---
4
5
 
5
6
  ## Must
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  description: A primitive with semantic meaning in a domain type, such as an id, email, URL, or port in a service's contract, an event, or a stored record, must be declared as a branded schema, never as a bare string or number.
3
+ reads: ["symbols", "references"]
3
4
  ---
4
5
 
5
6
  Two ids of different things are both strings, so nothing stops one being
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  description: Variants of a domain type, alternatives that carry fields and that a service returns, stores, or decodes, must be tagged schemas such as Schema.TaggedClass or Schema.TaggedStruct members of a Schema.Union, never TypeScript object types joined by a tag field.
3
+ reads: ["symbols", "references"]
3
4
  ---
4
5
 
5
6
  A tagged schema decodes each variant at the boundary and lets `Match.tag`
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  description: A type that describes the same data as another type, a schema, a function, or a constant should be derived from it, with a utility type such as `Pick`, with `typeof` or `keyof`, or as a schema's inferred type, and should not restate its fields or values by hand.
3
3
  level: warning
4
+ reads: ["symbols", "references"]
4
5
  ---
5
6
 
6
7
  ## Should
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: adhere
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.
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 repos and lint packages, 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
@@ -19,10 +19,13 @@ first rules, first lint, and CI, and adhere-fix verifies and fixes findings.
19
19
  ## Commands
20
20
 
21
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
22
+ adhere init [--force] # config, tsconfig, two example rules, and the dev dependency
23
+ adhere init --shared org/repo # rules, instructions, lint package, CI, and an alchemy stack
24
+ adhere postinstall # render .adhere/instructions/**/*.md into AGENTS.md
24
25
  adhere login # save a TypeSafe AI API key; TYPESAFE_API_KEY takes precedence
25
26
  adhere validate # rule wording, rules a linter could check, contradictions
27
+ adhere rules create [idea] # native rule, approved plugin, or rule file; org PR
28
+ adhere rules readme [org/repo] # refresh a shared repo README's managed guide
26
29
  adhere lint # judge the working directory; exit 1 on an error finding
27
30
  adhere lint --limit 0 # compiler-free counts/cache plan; no budgeting
28
31
  adhere lint --limit 100 # first 100 files needing requests; all pending rules per file
@@ -32,16 +35,17 @@ adhere lint --preset effect # add a built-in rule set; the config becomes o
32
35
  adhere lint --filter 'src/**' # read only matching files; repeat, and ! to leave out
33
36
  adhere lint --threshold 0.9 # report at another threshold from the cache; judges nothing again
34
37
  adhere lint --deny-warnings # fail on warnings too
35
- adhere list org/repo # the rules in a GitHub repo's .adhere/rules/
36
- adhere install org/repo[/topic[/rule]][#ref] # copy them into .adhere/rules/org/repo/
38
+ adhere list org/repo # the rules in a GitHub repo's .adhere/rules/, with descriptions
39
+ adhere install org/repo[/topic[/rule]][#ref] [--all] # copy instructions and rules into .adhere/
37
40
  adhere skill [docs|setup|fix] # print a skill
38
41
  ```
39
42
 
40
43
  `adhere <command> --help` lists every flag. Version, help, skills, `validate`,
41
44
  and planning with `--limit 0` do not start the native compiler. Default `lint`
42
- is compiler-free. With `typeAware: true` or `--type-aware`, it starts the compiler
43
- only during execution, after confirmation, for pending judgments or cached
44
- findings still needing location.
45
+ is compiler-free, and leaves out bundled preset rules that declare native
46
+ reads. With `typeAware: true` or `--type-aware`, it includes them and starts the
47
+ compiler only during execution, after confirmation, for pending judgments or
48
+ cached findings still needing location.
45
49
 
46
50
  `--limit` now counts files, not rule checks; use the same flag without an extra
47
51
  unit selector. Each request-bearing file consumes one slot for all its pending
@@ -58,11 +62,11 @@ native-context caveat before execution.
58
62
  A repo's rules live in `.adhere/rules/`, a directory per rule holding a
59
63
  `RULE.md`, or a `RULE.ts` (below). The directory's path is the rule id:
60
64
  `.adhere/rules/data/brand-ports/RULE.md` is `data/brand-ports`. Other files in
61
- a rule's directory are not rules. A `*.md` rule elsewhere in `.adhere/`, as
62
- rules were once written, refuses the run with where it goes to keep its id.
63
- A `.adhere/rules/` in a subdirectory holds rules for that
64
- subtree only, and the most specific rule with an id wins. `cache/` and the
65
- config in `.adhere/` are not rules.
65
+ a rule's directory are not rules, and only `RULE.md` or `RULE.ts` in a rule's
66
+ directory defines one: other Markdown in `.adhere/`, such as
67
+ `.adhere/instructions/`, is ignored. A `.adhere/rules/` in a subdirectory holds
68
+ rules for that subtree only, and the most specific rule with an id wins.
69
+ `cache/` and the config in `.adhere/` are not rules.
66
70
 
67
71
  ````md
68
72
  ---
@@ -123,6 +127,13 @@ Front matter besides `description`, all optional:
123
127
  only when Jev says the code that breaks the rule is as every `appliesTo`
124
128
  describes and as no `excludeIf` does.
125
129
 
130
+ - `reads: ["symbols", "references"]` declares the context the rule needs, a
131
+ JSON array on one line. It enables nothing: the config's `reads` and
132
+ `typeAware` still choose what this run sends. A project's own rule still
133
+ runs and lint warns that it may be less accurate until a declared read is
134
+ enabled; a bundled preset rule that declares either native read is left out
135
+ unless `typeAware` is on.
136
+
126
137
  A rule's directory can hold a `RULE.ts` instead, which default-exports
127
138
  `defineRule({...})` from `@drkmttr/adhere`, with the fields a config's inline
128
139
  rule takes:
@@ -173,9 +184,13 @@ Opting in with `typeAware: true` or `--type-aware` enables both `symbols` and
173
184
  `references`, regardless of `reads`. Legacy native-reader entries in `reads`
174
185
  alone do not enable the compiler; leaving them out does not disable it when
175
186
  type awareness is on. Code alone requires `reads: []` and type awareness off.
176
- Presets do not own or override this selection. Rule-level `reads`, in Markdown
177
- front matter, `defineRule`, or inline rules, is rejected with guidance to move
178
- it to the config. The context maps are:
187
+ Presets do not own or override this selection. A rule may declare what it
188
+ needs with `reads`, in Markdown front matter, `defineRule`, or an inline rule;
189
+ the declaration enables nothing. A bundled preset rule that declares `symbols`
190
+ or `references` is left out unless `typeAware` is on, so it is never judged
191
+ with less than it declares. A project's own rule still runs, and a declared
192
+ read the run does not enable is a warning that it may be less accurate until
193
+ enabled. The context maps are:
179
194
 
180
195
  - `workspacePackages`: only packages matched by this file's literal imports,
181
196
  as `{ canonicalPackageName: directory }`. Subpaths match the owning package;
@@ -301,17 +316,166 @@ https://github.com/darkmatter/adhere/tree/main/presets. A preset leaves out
301
316
  what that ecosystem's linters check exactly. Quiet a preset rule with
302
317
  `overrides` rather than copying it.
303
318
 
304
- ## Shared rules
319
+ ## Linter-checkable rules
320
+
321
+ A convention a linter can decide exactly does not belong in a `RULE.md`.
322
+ `adhere rules create` asks the coding agent for one of three routes:
323
+
324
+ - **A native Oxlint rule.** The agent answers `linter` as a plain rule-id
325
+ string, or null, and adhere checks it against its bundled Oxlint catalog at
326
+ decode time; unknown ids are rejected. It also supplies the rule's exact
327
+ option values, which the config puts after the severity: a rule whose
328
+ defaults enforce the idea prints `"error"`, while an option-dependent rule
329
+ prints `["error", ...options]`, because bare `"error"` would enforce
330
+ nothing. Banning lodash with `no-restricted-imports` becomes
331
+ `["error", { "paths": [{ "name": "lodash" }] }]`. A project rule prints the
332
+ exact config; an organization rule enables it in the shared `oxlint/rules.json`
333
+ and pushes a branch for a pull request with the README guide refreshed. In a
334
+ shared repo itself, "this project" writes `oxlint/rules.json` locally instead
335
+ of printing the config. A rule from a plugin Oxlint leaves off
336
+ by default also needs that plugin in `plugins`, whose list replaces Oxlint's
337
+ default set. A type-aware rule needs
338
+ `options.typeAware` and `oxlint-tsgolint`, which Vite+ includes.
339
+ - **A custom Oxlint JS plugin.** Only when the convention is visible in the
340
+ syntax tree and scopes: a JS plugin cannot see TypeScript types, other
341
+ files, or another parser's AST. The plugin must be plain ESM, default-export
342
+ its object, and import nothing; the agent reports each diagnostic's message
343
+ literally, so its valid and invalid fixtures can match exact diagnostics.
344
+ adhere shows the plugin and fixtures, and runs nothing until the user
345
+ approves. It then runs the fixtures with the project's own Oxlint RuleTester,
346
+ through `oxlint/plugins-dev` or `vite-plus/lint/plugins-dev`, so the project
347
+ needs its own Oxlint or Vite+ and Node 22 or newer on `PATH`; Oxlint's
348
+ RuleTester refuses Bun and older Node, there is no Bun fallback, and without
349
+ it nothing is written and no repair is attempted. adhere bundles no Oxlint.
350
+ One failure goes back to the agent to repair; the user approves the repaired
351
+ plugin, then it runs again. Nothing is written unless the tests pass.
352
+ - **An adhere rule**, for judgment, facts outside the file, or types.
353
+
354
+ Whichever route it takes, the draft is trialled on real files before anything
355
+ is written: the first 100 applicable files by default, the entire repo, or a
356
+ number the user types, with the plan's known cost shown first. The authoring
357
+ agent reads the saved report in `.adhere/trial/` and the source it names, then
358
+ the user accepts the findings, reviews each as correct, a false positive, or
359
+ unsure, or asks for a revision. Findings and examples take optional notes for
360
+ the agent; a revision is re-approved and reruns the same files, so the new
361
+ evidence is comparable. Skipping is allowed, but the write prompt then says
362
+ `(not trialled)` and the closing line says `Not trialled: …`; nothing is called
363
+ validated on a skipped, partial, or stale run. The trial runs for native
364
+ settings and approved plugins too.
365
+
366
+ A project plugin and its checked-in test go to
367
+ `.adhere/oxlint/<plugin>.mjs` and `.adhere/oxlint/<plugin>.test.mjs`; the
368
+ `.mjs` keeps ESM in a `type: commonjs` package. adhere prints the config to add
369
+ to `oxlint.config.ts` or Vite+'s lint block:
370
+
371
+ ```json
372
+ {
373
+ "jsPlugins": ["./.adhere/oxlint/no-foo.mjs"],
374
+ "rules": { "no-foo/no-foo": "error" }
375
+ }
376
+ ```
377
+
378
+ Run the test with Node 22 or newer: `node .adhere/oxlint/no-foo.test.mjs`.
379
+
380
+ A native project rule without options prints only its id:
381
+
382
+ ```json
383
+ { "rules": { "typescript/no-explicit-any": "error" } }
384
+ ```
385
+
386
+ A shared repo keeps plugins in `oxlint/plugins/` as `.js`, with `.test.js` beside
387
+ them, because the shared package is `type: module`; it registers them in
388
+ `oxlint/rules.json`. The catalog follows adhere's toolchain, not the project's
389
+ installed Oxlint, so a suggestion may be missing on an older Oxlint or a newer
390
+ rule may be absent from the catalog; the plugin tests always use the project's
391
+ Oxlint. `adhere validate` reports rules left over that a linter could check.
392
+
393
+ ## Shared repos
394
+
395
+ An organization keeps its instructions, rules, and lint settings in one repo,
396
+ scaffolded by `adhere init --shared org/repo`. Its `README.md` carries a
397
+ managed guide between `<!-- adhere:rules:start -->` and
398
+ `<!-- adhere:rules:end -->`: a contents list nesting every rule under its
399
+ topic, the rules grouped by topic with each bold statement and rule id opening
400
+ that rule's `RULE.md` verbatim: examples and explanation, then a source link
401
+ under a stable permalink, every enabled native setting in `oxlint/rules.json`
402
+ with its severity, and each custom plugin's enabled rules, linked to
403
+ `oxlint/plugins/<name>.js` when that is a local file. A `RULE.ts` is linked as
404
+ TypeScript source only: the guide never reads or runs it, and plugin code is
405
+ never loaded. Refresh it with `adhere rules readme [org/repo]` after renaming
406
+ or deleting a rule; it preserves prose outside the block, omits off lint
407
+ settings, and refuses malformed markers without writing.
408
+ `adhere rules create` refreshes it for local writes and stages it with
409
+ shared-repo branches.
410
+
411
+ The command chooses its target by argument, then by where it runs. A named
412
+ `org/repo` wins. Otherwise a working directory that is itself a shared repo
413
+ (`package.json` exporting `./oxlint`) is written in place. Anywhere else,
414
+ adhere reads the checkout's GitHub `origin` (`https`, `ssh`, or scp form) and
415
+ infers `<owner>/standards` (`acme/app` -> `acme/standards`, never `acme/app`);
416
+ a fork's owner is the fork's. That remote target is cloned at its default
417
+ branch, the changed block is committed alone on a fresh
418
+ `adhere/readme-<timestamp>` branch, pushed, and printed with a compare link.
419
+ No pull request is opened, the consumer checkout and the default branch stay
420
+ untouched, and an unchanged guide prints `No change, so nothing was pushed.`
421
+ without committing. A rules-only remote works, since the `./oxlint` export is
422
+ only required for the local write. A missing or non-GitHub `origin` fails and
423
+ names `adhere rules readme org/repo`; name the repo explicitly when the
424
+ organization's repo is not `<owner>/standards`.
425
+
426
+ Other repos copy the first two with `adhere install`:
427
+
428
+ ```sh
429
+ adhere list org/repo # rules and descriptions; instructions are not listed
430
+ adhere install org/repo # choose instruction files, then rules, at a terminal
431
+ adhere install org/repo --all # everything, without a terminal (CI)
432
+ adhere install org/repo/data # choose among the data topic's rules
433
+ adhere install org/repo/data/ports # exactly that rule, without asking
434
+ adhere install org/repo#v1.0.0 --all # everything at tag v1.0.0
435
+ ```
436
+
437
+ A bare repo chooses from instruction files and rules. A topic chooses only the
438
+ rules under it, never instructions, even a topic named `instructions`. An exact
439
+ rule id with no rule deeper under it is copied directly. Already-installed
440
+ items are preselected. A rerun skips what exists; `--force` replaces existing
441
+ files and rule directories, and deselecting never deletes them.
442
+
443
+ Instruction files land in `.adhere/instructions/org/repo/<path>`. Rules keep
444
+ their whole directory in `.adhere/rules/org/repo/`, so `data/brand-ports`
445
+ becomes the rule `org/repo/data/brand-ports`, and a copied `RULE.ts` runs
446
+ whenever lint does. Copies are the repo's own from then on. `adhere postinstall`
447
+ renders the instruction files into `AGENTS.md`; files whose path has at least
448
+ three segments come first (copied files, and local files two directories deep),
449
+ then files directly in the instructions directory, then files one directory
450
+ deep, each group sorted by path.
451
+
452
+ The shared `oxlint/` package is not copied. Pin it as a git dependency at a
453
+ repo-wide tag (there are no per-rule or per-package tags; one tag covers rules,
454
+ instructions, and lint together), then spread it into Vite+ or plain Oxlint:
455
+
456
+ ```ts
457
+ // vite.config.ts
458
+ import sharedLint from "@org/repo/oxlint";
459
+ import { defineConfig } from "vite-plus";
460
+
461
+ export default defineConfig({
462
+ lint: { ...sharedLint, jsPlugins: [...sharedLint.jsPlugins, "./.adhere/oxlint/local.mjs"] },
463
+ });
464
+ ```
465
+
466
+ ```ts
467
+ // oxlint.config.ts: oxlint -c oxlint.config.ts
468
+ import sharedLint from "@org/repo/oxlint";
469
+
470
+ export default { ...sharedLint };
471
+ ```
472
+
473
+ `oxlint/index.js` reads `oxlint/rules.json` and resolves plugin paths relative to
474
+ the package. Paths a project adds stay relative to its own config; project
475
+ plugins live in `.adhere/oxlint/`.
305
476
 
306
- An organization keeps rules in one repo's `.adhere/rules/`, scaffolded by
307
- `adhere init --shared org/repo`, and other repos copy them in with
308
- `adhere install org/repo`, or one topic or rule with
309
- `adhere install org/repo/<topic>[/<rule>]`. Each rule's whole directory lands
310
- in `.adhere/rules/org/repo/`, so `data/brand-ports` becomes the rule
311
- `org/repo/data/brand-ports`, and a copied `RULE.ts` runs whenever lint does.
312
- They are the repo's own rules from then on; a second install skips existing
313
- rules unless `--force`, which replaces their directories, local edits and all. The source is cloned with git,
314
- so a private repo needs git's credentials for GitHub.
477
+ The source is cloned with git, so a private repo needs git's credentials for
478
+ GitHub.
315
479
 
316
480
  ## Comments and suppressions
317
481
 
@@ -346,9 +510,10 @@ re-judges that rule; a threshold change re-judges nothing.
346
510
 
347
511
  Dependencies, related source, reader selection, shared read-context contents,
348
512
  and hook-owned external data are deliberately not fingerprinted. If `A.ts` imports
349
- `B.ts`, changing `B.ts` does not invalidate `A.ts`. Changing config `reads` or
350
- `typeAware` does not refresh its cached answers either. This keeps planning cheap but accepts
351
- stale dependency/context judgments.
513
+ `B.ts`, changing `B.ts` does not invalidate `A.ts`. Changing the config's
514
+ `reads` or `typeAware` re-judges every rule once; changing what those reads
515
+ find does not. This keeps planning cheap but accepts stale dependency/context
516
+ judgments.
352
517
 
353
518
  `planAudit` reads/counts/hashes code, checks the cache, and makes code plus cheap
354
519
  workspace estimates without compiler or `SourceReads` calls. It applies `--limit`
@@ -163,7 +163,7 @@ rules, and in the report give the user the counts and suggest one of:
163
163
  `{ threshold: 0.9 }`;
164
164
  - for a project rule, rewording it to say what code it is about and what is
165
165
  out of scope, following the rule writing tips:
166
- https://github.com/darkmatter/adhere#rule-writing-tips.
166
+ https://dm.sh/adhere/writing-good-rules/#rule-writing-tips.
167
167
 
168
168
  ## 7. Report
169
169