@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.
- package/README.md +62 -1575
- package/package.json +18 -15
- package/presets/README.md +140 -0
- package/presets/alchemy/runtime/bind-tag-not-implementation/RULE.md +1 -0
- package/presets/effect/data/brand-meaningful-primitives/RULE.md +1 -0
- package/presets/effect/data/variants-are-tagged-unions/RULE.md +1 -0
- package/presets/typescript/types/derive-dont-restate/RULE.md +1 -0
- package/skills/adhere/SKILL.md +194 -29
- package/skills/adhere-fix/SKILL.md +1 -1
- package/skills/adhere-setup/SKILL.md +60 -26
- package/src/cli.ts +334 -36
- package/src/config.ts +173 -142
- package/src/create/agents.ts +149 -0
- package/src/create/ask.ts +363 -0
- package/src/create/examples.ts +94 -0
- package/src/create/look.ts +191 -0
- package/src/create/oxlint-trial.ts +464 -0
- package/src/create/oxlint.ts +1012 -0
- package/src/create/trial-review.ts +562 -0
- package/src/create/trial.ts +611 -0
- package/src/create/wizard.ts +466 -0
- package/src/index.ts +2 -0
- package/src/init.ts +171 -41
- package/src/install.ts +296 -94
- package/src/instructions.ts +98 -0
- package/src/markdown.ts +15 -11
- package/src/oxlint/catalog.json +7844 -0
- package/src/oxlint/catalog.ts +72 -0
- package/src/oxlint/shared.ts +140 -0
- package/src/readme-guide.ts +431 -0
- package/src/readme.ts +291 -0
- package/src/rules.ts +42 -45
- package/src/services/AdhereConfig.ts +67 -21
- package/src/services/AuditCache.ts +176 -169
- package/src/services/Credentials.ts +14 -8
- package/src/services/Jev.reads.test.ts +22 -0
- package/src/services/Jev.ts +35 -13
- package/src/services/SourceWalker.ts +12 -5
- package/src/wording.ts +4 -4
- package/src/workflows/audit.ts +85 -14
- package/src/workflows/format.ts +98 -1
- package/src/workspaces.test.ts +37 -1
- package/src/workspaces.ts +121 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drkmttr/adhere",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
43
|
+
"effect": "4.0.3",
|
|
44
|
+
"mdast-util-from-markdown": "^2.1.0"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
|
-
"@effect/platform-bun": "4.0.
|
|
47
|
+
"@effect/platform-bun": "4.0.3",
|
|
45
48
|
"@types/bun": "^1.4.2",
|
|
46
|
-
"@types/node": "^
|
|
49
|
+
"@types/node": "^26.0.0",
|
|
47
50
|
"release-it": "^21.1.0",
|
|
48
|
-
"typescript": "7.1.0-dev.
|
|
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.
|
|
54
|
+
"vitest": "5.0.5"
|
|
52
55
|
},
|
|
53
56
|
"overrides": {
|
|
54
|
-
"@effect/platform-node-shared": "4.0.
|
|
57
|
+
"@effect/platform-node-shared": "4.0.3",
|
|
55
58
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
|
|
56
|
-
"vitest": "5.0.
|
|
59
|
+
"vitest": "5.0.5"
|
|
57
60
|
},
|
|
58
61
|
"devEngines": {
|
|
59
62
|
"packageManager": {
|
|
60
63
|
"name": "bun",
|
|
61
|
-
"version": "1.4.
|
|
64
|
+
"version": "1.4.3",
|
|
62
65
|
"onFail": "download"
|
|
63
66
|
},
|
|
64
67
|
"runtime": {
|
|
65
68
|
"name": "node",
|
|
66
|
-
"version": "
|
|
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.
|
|
94
|
-
"@drkmttr/adhere-darwin-x64": "0.
|
|
95
|
-
"@drkmttr/adhere-linux-arm64": "0.
|
|
96
|
-
"@drkmttr/adhere-linux-x64": "0.
|
|
97
|
-
"@drkmttr/adhere-win32-x64": "0.
|
|
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
|
package/skills/adhere/SKILL.md
CHANGED
|
@@ -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
|
|
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] #
|
|
23
|
-
adhere init --shared org/repo #
|
|
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]
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
A `.adhere/rules/` in a subdirectory holds
|
|
64
|
-
subtree only, and the most specific rule with an id wins.
|
|
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.
|
|
177
|
-
front matter, `defineRule`, or inline
|
|
178
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
307
|
-
|
|
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
|
|
350
|
-
`
|
|
351
|
-
stale dependency/context
|
|
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://
|
|
166
|
+
https://dm.sh/adhere/writing-good-rules/#rule-writing-tips.
|
|
167
167
|
|
|
168
168
|
## 7. Report
|
|
169
169
|
|