@drkmttr/adhere 0.9.5 → 0.10.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 (83) hide show
  1. package/README.md +245 -65
  2. package/package.json +6 -6
  3. package/presets/alchemy/apis/match-remote-errors-by-tag.md +6 -2
  4. package/presets/alchemy/apis/schemas-at-trust-boundaries.md +6 -2
  5. package/presets/alchemy/data/commit-migrations-then-deploy.md +6 -2
  6. package/presets/alchemy/data/hyperdrive-direct-origin.md +6 -2
  7. package/presets/alchemy/data/migrations-use-direct-url.md +6 -2
  8. package/presets/alchemy/data/never-disable-tls-verification.md +6 -2
  9. package/presets/alchemy/data/parameterized-sql.md +6 -2
  10. package/presets/alchemy/data/private-endpoint-at-runtime.md +6 -2
  11. package/presets/alchemy/data/single-migration-owner.md +6 -2
  12. package/presets/alchemy/durable/idempotent-event-handlers.md +6 -2
  13. package/presets/alchemy/durable/io-inside-steps.md +6 -2
  14. package/presets/alchemy/durable/no-self-triggering-bucket-writes.md +6 -2
  15. package/presets/alchemy/durable/stable-step-names.md +6 -2
  16. package/presets/alchemy/durable/webhook-state-is-truth.md +6 -2
  17. package/presets/alchemy/providers/diff-guards-unresolved-props.md +6 -2
  18. package/presets/alchemy/providers/idempotent-delete.md +8 -3
  19. package/presets/alchemy/providers/lazy-layer-construction.md +6 -2
  20. package/presets/alchemy/providers/reconcile-from-observed-state.md +6 -2
  21. package/presets/alchemy/resources/anchor-paths-with-import-meta.md +6 -2
  22. package/presets/alchemy/resources/idempotent-actions-and-seeds.md +6 -2
  23. package/presets/alchemy/resources/previews-reference-shared-resources.md +6 -2
  24. package/presets/alchemy/resources/protect-irreplaceable-data.md +6 -2
  25. package/presets/alchemy/resources/remote-state-for-ci.md +6 -2
  26. package/presets/alchemy/resources/stable-logical-ids.md +6 -2
  27. package/presets/alchemy/resources/stage-unique-physical-names.md +6 -2
  28. package/presets/alchemy/runtime/bind-tag-not-implementation.md +6 -2
  29. package/presets/alchemy/runtime/deterministic-config-defaults.md +6 -2
  30. package/presets/alchemy/runtime/disposables-in-request-scope.md +6 -2
  31. package/presets/alchemy/runtime/lambda-no-post-response-work.md +6 -2
  32. package/presets/alchemy/runtime/no-per-request-work-at-construction.md +6 -2
  33. package/presets/alchemy/runtime/resolve-config-and-bindings-at-construction.md +6 -2
  34. package/presets/alchemy/secrets/let-alchemy-mint-infra-secrets.md +6 -2
  35. package/presets/alchemy/secrets/never-logged-returned-or-output.md +6 -2
  36. package/presets/alchemy/secrets/none-in-browser-env.md +6 -2
  37. package/presets/alchemy/secrets/redacted-until-use.md +6 -2
  38. package/presets/alchemy/secrets/unwrap-before-compare-or-send.md +6 -2
  39. package/presets/alchemy/security/authorize-public-endpoints.md +6 -2
  40. package/presets/alchemy/security/github-secret-not-variable.md +6 -2
  41. package/presets/alchemy/security/identity-from-verified-session.md +6 -2
  42. package/presets/alchemy/security/keep-auth-protections-on.md +6 -2
  43. package/presets/alchemy/security/least-privilege-bindings.md +6 -2
  44. package/presets/alchemy/security/least-privilege-ci-credentials.md +6 -2
  45. package/presets/alchemy/security/lookup-failure-is-not-anonymous.md +6 -2
  46. package/presets/effect/basics/external-calls-are-resilient.md +6 -2
  47. package/presets/effect/basics/gen-for-sequencing.md +6 -2
  48. package/presets/effect/basics/instrument-with-pipe.md +6 -2
  49. package/presets/effect/config/business-logic-depends-on-config-service.md +6 -2
  50. package/presets/effect/config/secrets-are-redacted.md +6 -2
  51. package/presets/effect/config/tests-provide-values-directly.md +7 -2
  52. package/presets/effect/config/validate-with-schema.md +6 -2
  53. package/presets/effect/data/brand-meaningful-primitives.md +6 -2
  54. package/presets/effect/data/variants-are-tagged-unions.md +6 -2
  55. package/presets/effect/errors/catch-defects-at-boundaries-only.md +6 -2
  56. package/presets/effect/errors/defects-for-bugs.md +6 -2
  57. package/presets/effect/errors/wrap-external-errors.md +6 -2
  58. package/presets/effect/services/dependencies-through-layers.md +6 -2
  59. package/presets/effect/services/fresh-layer-per-test.md +7 -2
  60. package/presets/effect/services/memoize-parameterized-layers.md +6 -2
  61. package/presets/effect/services/no-mutable-state.md +6 -2
  62. package/presets/effect/services/operations-have-no-requirements.md +6 -2
  63. package/presets/effect/services/test-layers-are-in-memory.md +7 -2
  64. package/presets/effect/testing/test-clock-for-time.md +7 -2
  65. package/skills/adhere/SKILL.md +40 -12
  66. package/skills/adhere-fix/SKILL.md +174 -0
  67. package/src/cli.ts +51 -14
  68. package/src/comments.ts +36 -0
  69. package/src/config.ts +98 -14
  70. package/src/contradictions.ts +10 -2
  71. package/src/excerpt.ts +11 -1
  72. package/src/index.ts +1 -1
  73. package/src/init.ts +119 -12
  74. package/src/main.ts +0 -1
  75. package/src/markdown.ts +77 -14
  76. package/src/models/Audit.ts +2 -0
  77. package/src/presets.ts +6 -3
  78. package/src/rules.ts +35 -2
  79. package/src/services/AdhereConfig.ts +36 -13
  80. package/src/services/SourceWalker.ts +32 -10
  81. package/src/suppress.ts +113 -0
  82. package/src/workflows/audit.ts +99 -17
  83. package/src/workflows/format.ts +20 -13
@@ -2,11 +2,15 @@
2
2
  description: A sensitive CI value, such as a token, key, password, or connection string, must be stored with GitHub.Secret, never with GitHub.Variable, which is plain text and visible in logs.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  yield* GitHub.Secret("CloudflareToken", { owner, repository, name: "CLOUDFLARE_API_TOKEN", value: token.value });
7
9
  yield* GitHub.Variable("AwsRegion", { owner, repository, name: "AWS_REGION", value: "us-east-1" });
8
10
  ```
9
11
 
10
- ```ts never
12
+ ## Never
13
+
14
+ ```ts
11
15
  yield* GitHub.Variables({ owner, repository, variables: { CLOUDFLARE_API_TOKEN: token.value } });
12
16
  ```
@@ -2,13 +2,17 @@
2
2
  description: The acting user must come from the session or token verified in that request, never from an ID in the request body, query, or headers, and never from a user cached in a process-wide layer.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const session = yield* auth.getSession();
7
9
  if (session === null) return yield* Effect.fail(new Unauthorized());
8
10
  const rows = yield* sql`SELECT * FROM uploads WHERE owner_id = ${session.user.id}`;
9
11
  ```
10
12
 
11
- ```ts never
13
+ ## Never
14
+
15
+ ```ts
12
16
  const { ownerId } = yield* HttpServerRequest.schemaBodyJson(Schema.Struct({ ownerId: Schema.String }));
13
17
  const rows = yield* sql`SELECT * FROM uploads WHERE owner_id = ${ownerId}`;
14
18
  ```
@@ -2,7 +2,9 @@
2
2
  description: Auth configuration must keep state, PKCE, origin and CSRF checks, ID-token verification, and email verification enabled, never disable them or merge accounts by email alone to work around an error or a cross-origin setup.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const auth = yield* BetterAuth({
7
9
  trustedOrigins: [siteOrigin],
8
10
  emailAndPassword: { enabled: true, requireEmailVerification: true },
@@ -10,7 +12,9 @@ const auth = yield* BetterAuth({
10
12
  });
11
13
  ```
12
14
 
13
- ```ts never
15
+ ## Never
16
+
17
+ ```ts
14
18
  const auth = yield* BetterAuth({
15
19
  trustedOrigins: ["*"],
16
20
  advanced: { disableCSRFCheck: true },
@@ -2,12 +2,16 @@
2
2
  description: A runtime should bind the narrowest capability its calls need, a Read or Write binding when it only reads or only writes, and should not bind ReadWrite for one-sided use.
3
3
  ---
4
4
 
5
- ```ts should
5
+ ## Should
6
+
7
+ ```ts
6
8
  const bucket = yield* Cloudflare.R2.ReadBucket(Uploads);
7
9
  return { fetch: Effect.gen(function* () { return yield* bucket.get("report.json"); }) };
8
10
  ```
9
11
 
10
- ```ts should not
12
+ ## Should not
13
+
14
+ ```ts
11
15
  const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads); // only get is ever called
12
16
  return { fetch: Effect.gen(function* () { return yield* bucket.get("report.json"); }) };
13
17
  ```
@@ -2,7 +2,9 @@
2
2
  description: Credentials a stack mints for CI should be scoped to what the app deploys, using a GitHub OIDC role pinned to the repository on AWS and a trimmed permission list on Cloudflare, and should not grant AdministratorAccess or unused write scopes.
3
3
  ---
4
4
 
5
- ```ts should
5
+ ## Should
6
+
7
+ ```ts
6
8
  const role = yield* AWS.IAM.Role("GitHubDeploy", {
7
9
  assumeRolePolicyDocument: { Version: "2012-10-17", Statement: [{
8
10
  Effect: "Allow", Principal: { Federated: oidc.openIDConnectProviderArn },
@@ -13,7 +15,9 @@ const role = yield* AWS.IAM.Role("GitHubDeploy", {
13
15
  });
14
16
  ```
15
17
 
16
- ```ts should not
18
+ ## Should not
19
+
20
+ ```ts
17
21
  const role = yield* AWS.IAM.Role("GitHubDeploy", {
18
22
  assumeRolePolicyDocument: { Version: "2012-10-17", Statement: [{
19
23
  Effect: "Allow", Principal: { Federated: oidc.openIDConnectProviderArn },
@@ -2,14 +2,18 @@
2
2
  description: Only a null session may be treated as anonymous; a failed session lookup must stay in the error channel or map to a server error, never be collapsed into signed out.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const session = yield* auth.getSession().pipe(
7
9
  Effect.mapError(() => new AuthenticationUnavailable()),
8
10
  );
9
11
  if (session === null) return yield* Effect.fail(new Unauthorized());
10
12
  ```
11
13
 
12
- ```ts never
14
+ ## Never
15
+
16
+ ```ts
13
17
  const session = yield* auth.getSession().pipe(Effect.orElseSucceed(() => null));
14
18
  if (session === null) return yield* Effect.fail(new Unauthorized());
15
19
  ```
@@ -2,7 +2,9 @@
2
2
  description: A call over the network, such as an HTTP request, a database query, or a third-party API, should carry a timeout and a retry schedule, and should not go out bare. A call through a client that already applies both, such as an HttpClient built with a timeout and retryTransient or an SDK with its own retries, has them. Calls through the platform FileSystem and Path services are local and are not in scope.
3
3
  ---
4
4
 
5
- ```ts should
5
+ ## Should
6
+
7
+ ```ts
6
8
  const retryPolicy = Schedule.exponential("100 millis").pipe(Schedule.both(Schedule.recurs(3)));
7
9
 
8
10
  const resilientCall = HttpClient.get("https://api.example.com/users").pipe(
@@ -12,6 +14,8 @@ const resilientCall = HttpClient.get("https://api.example.com/users").pipe(
12
14
  );
13
15
  ```
14
16
 
15
- ```ts should not
17
+ ## Should not
18
+
19
+ ```ts
16
20
  const response = yield* HttpClient.get(`https://api.example.com/users/${id}`);
17
21
  ```
@@ -2,7 +2,9 @@
2
2
  description: Sequential effectful steps must be written with Effect.gen and yield*, never as nested flatMap or callback chains.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const program = Effect.gen(function* () {
7
9
  const data = yield* fetchData;
8
10
  yield* Effect.logInfo(`Processing data: ${data}`);
@@ -10,7 +12,9 @@ const program = Effect.gen(function* () {
10
12
  });
11
13
  ```
12
14
 
13
- ```ts never
15
+ ## Never
16
+
17
+ ```ts
14
18
  const page = fetchUser(id).pipe(
15
19
  Effect.flatMap((user) =>
16
20
  fetchOrders(user.id).pipe(Effect.flatMap((orders) => render(user, orders))),
@@ -2,7 +2,9 @@
2
2
  description: Timeouts, retries, logging, and spans must be attached with .pipe, never written into the body of the effect.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const program = fetchData.pipe(
7
9
  Effect.timeout("5 seconds"),
8
10
  Effect.retry(Schedule.exponential("100 millis").pipe(Schedule.both(Schedule.recurs(3)))),
@@ -11,7 +13,9 @@ const program = fetchData.pipe(
11
13
  );
12
14
  ```
13
15
 
14
- ```ts never
16
+ ## Never
17
+
18
+ ```ts
15
19
  const report = Effect.gen(function* () {
16
20
  const rows = yield* Effect.timeout(loadRows, "5 seconds");
17
21
  return yield* Effect.withSpan(summarize(rows), "summarize");
@@ -2,7 +2,9 @@
2
2
  description: Business logic must depend on a config service. Config primitives must be read only inside that service's layer, never in business logic.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  class ApiConfig extends Context.Service<
7
9
  ApiConfig,
8
10
  {
@@ -21,7 +23,9 @@ class ApiConfig extends Context.Service<
21
23
  }
22
24
  ```
23
25
 
24
- ```ts never
26
+ ## Never
27
+
28
+ ```ts
25
29
  const sendInvoice = Effect.fn("sendInvoice")(function* (invoice: Invoice) {
26
30
  const apiUrl = yield* Config.string("BILLING_API_URL");
27
31
  return yield* post(apiUrl, invoice);
@@ -2,7 +2,9 @@
2
2
  description: A token, password, or key must be read with Config.redacted, never with Config.string.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const program = Effect.gen(function* () {
7
9
  const apiKey = yield* Config.redacted("API_KEY");
8
10
 
@@ -14,6 +16,8 @@ const program = Effect.gen(function* () {
14
16
  });
15
17
  ```
16
18
 
17
- ```ts never
19
+ ## Never
20
+
21
+ ```ts
18
22
  const token = yield* Config.string("GITHUB_TOKEN");
19
23
  ```
@@ -1,8 +1,11 @@
1
1
  ---
2
2
  description: A test should supply config with Layer.succeed on the config service, and should not set environment variables or a ConfigProvider to do it.
3
+ tests: only
3
4
  ---
4
5
 
5
- ```ts should
6
+ ## Should
7
+
8
+ ```ts
6
9
  Effect.runPromise(
7
10
  program.pipe(
8
11
  Effect.provide(
@@ -15,7 +18,9 @@ Effect.runPromise(
15
18
  );
16
19
  ```
17
20
 
18
- ```ts should not
21
+ ## Should not
22
+
23
+ ```ts
19
24
  beforeAll(() => {
20
25
  process.env.DATABASE_URL = "postgres://localhost/test";
21
26
  });
@@ -2,7 +2,9 @@
2
2
  description: A config value with constraints must be validated as it is read, with Config.schema or Config.mapOrFail, never checked by hand after reading.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const Port = Schema.NumberFromString.pipe(
7
9
  Schema.check(Schema.isInt()),
8
10
  Schema.check(Schema.isBetween({ minimum: 1, maximum: 65535 })),
@@ -28,7 +30,9 @@ const program = Effect.gen(function* () {
28
30
  });
29
31
  ```
30
32
 
31
- ```ts never
33
+ ## Never
34
+
35
+ ```ts
32
36
  const timeoutMs = yield* Config.number("TIMEOUT_MS");
33
37
  if (timeoutMs <= 0 || timeoutMs > 60_000) {
34
38
  return yield* Effect.die("TIMEOUT_MS out of range");
@@ -2,7 +2,9 @@
2
2
  description: A primitive with semantic meaning, such as an id, email, URL, port, or count, must be a branded schema, never a bare string or number.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  export const UserId = Schema.String.pipe(Schema.brand("UserId"));
7
9
  export type UserId = typeof UserId.Type;
8
10
 
@@ -16,7 +18,9 @@ export const Port = Schema.Int.pipe(
16
18
  export type Port = typeof Port.Type;
17
19
  ```
18
20
 
19
- ```ts never
21
+ ## Never
22
+
23
+ ```ts
20
24
  type AccountId = string;
21
25
 
22
26
  const Session = Schema.Struct({
@@ -2,7 +2,9 @@
2
2
  description: Structured variants, alternatives that carry fields, must be Schema.TaggedClass members of a Schema.Union, never hand-written object types joined by a tag field. Simple alternatives without fields may be Schema.Literals.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  export class Success extends Schema.TaggedClass<Success>("Success")("Success", {
7
9
  value: Schema.Number,
8
10
  }) {}
@@ -22,7 +24,9 @@ const renderResult = (result: Result) =>
22
24
  );
23
25
  ```
24
26
 
25
- ```ts never
27
+ ## Never
28
+
29
+ ```ts
26
30
  type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
27
31
 
28
32
  const area = (shape: Shape) =>
@@ -2,7 +2,9 @@
2
2
  description: Defects must be caught only at a system boundary for logging or shutdown, never in business logic.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  // At app entry: if config fails, nothing can proceed
7
9
  const main = Effect.gen(function* () {
8
10
  const config = yield* loadConfig.pipe(Effect.orDie);
@@ -10,6 +12,8 @@ const main = Effect.gen(function* () {
10
12
  });
11
13
  ```
12
14
 
13
- ```ts never
15
+ ## Never
16
+
17
+ ```ts
14
18
  const price = computePrice(cart).pipe(Effect.catchDefect(() => Effect.succeed(0)));
15
19
  ```
@@ -2,7 +2,9 @@
2
2
  description: A typed error must be for a failure the caller can handle. A bug or invariant violation must be a defect, never a typed error.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const recovered: Effect.Effect<string, ValidationError> = program.pipe(
7
9
  Effect.catchTag("HttpError", (error) =>
8
10
  Effect.gen(function* () {
@@ -18,7 +20,9 @@ const main = Effect.gen(function* () {
18
20
  });
19
21
  ```
20
22
 
21
- ```ts never
23
+ ## Never
24
+
25
+ ```ts
22
26
  class CacheCorrupted extends Schema.TaggedError<CacheCorrupted>()("CacheCorrupted", {
23
27
  key: Schema.String,
24
28
  }) {}
@@ -2,7 +2,9 @@
2
2
  description: An error from an external library must be wrapped in a tagged error with a Schema.Defect field, never passed through raw.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  class ApiError extends Schema.TaggedError<ApiError>()("ApiError", {
7
9
  endpoint: Schema.String,
8
10
  statusCode: Schema.Number,
@@ -23,7 +25,9 @@ const fetchUser = (id: string) =>
23
25
  );
24
26
  ```
25
27
 
26
- ```ts never
28
+ ## Never
29
+
30
+ ```ts
27
31
  const charge = (amount: number) =>
28
32
  Effect.tryPromise(() => stripe.charges.create({ amount, currency: "usd" }));
29
33
  ```
@@ -2,7 +2,9 @@
2
2
  description: A service that depends on other services must be a Context.Service built by a Layer that yields those services as it is constructed, never a factory function or class that takes services, clients, or their implementations as arguments.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  class Users extends Context.Service<
7
9
  Users,
8
10
  {
@@ -26,7 +28,9 @@ export const UsersLive = Layer.effect(
26
28
  );
27
29
  ```
28
30
 
29
- ```ts never
31
+ ## Never
32
+
33
+ ```ts
30
34
  export const makeUsers = (database: Database["Service"], logger: Logger["Service"]) => ({
31
35
  find: (id: UserId) =>
32
36
  Effect.gen(function* () {
@@ -1,8 +1,11 @@
1
1
  ---
2
2
  description: Each it.effect must provide its own layer. it.layer must be used only to share an expensive resource across a suite, never a cheap one.
3
+ tests: only
3
4
  ---
4
5
 
5
- ```ts must
6
+ ## Must
7
+
8
+ ```ts
6
9
  it.effect("starts at zero", () =>
7
10
  Effect.gen(function* () {
8
11
  const counter = yield* Counter;
@@ -19,7 +22,9 @@ it.effect("increments without leaking", () =>
19
22
  );
20
23
  ```
21
24
 
22
- ```ts never
25
+ ## Never
26
+
27
+ ```ts
23
28
  it.layer(Counter.layer)("Counter", (it) => {
24
29
  it.effect("starts at zero", () => Effect.gen(function* () { /* ... */ }));
25
30
  it.effect("increments", () => Effect.gen(function* () { /* ... */ }));
@@ -2,7 +2,9 @@
2
2
  description: The result of a parameterized layer constructor must be stored in a module constant before it is used in more than one place, never constructed again for each use.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  const postgresLayer = Postgres.layer({ url: "postgres://localhost/mydb", poolSize: 10 });
7
9
 
8
10
  const goodAppLayer = Layer.merge(
@@ -11,7 +13,9 @@ const goodAppLayer = Layer.merge(
11
13
  );
12
14
  ```
13
15
 
14
- ```ts never
16
+ ## Never
17
+
18
+ ```ts
15
19
  const Live = Layer.merge(
16
20
  Sessions.layer.pipe(Layer.provide(Redis.layer({ host: "localhost" }))),
17
21
  RateLimits.layer.pipe(Layer.provide(Redis.layer({ host: "localhost" }))),
@@ -2,7 +2,9 @@
2
2
  description: A service must expose only readonly members, never mutable state.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  class Logger extends Context.Service<
7
9
  Logger,
8
10
  {
@@ -11,7 +13,9 @@ class Logger extends Context.Service<
11
13
  >()("@app/Logger") {}
12
14
  ```
13
15
 
14
- ```ts never
16
+ ## Never
17
+
18
+ ```ts
15
19
  class Counter extends Context.Service<
16
20
  Counter,
17
21
  { count: number; readonly increment: () => Effect.Effect<void> }
@@ -2,7 +2,9 @@
2
2
  description: A service's operations must have no requirements, typed Effect<A, E, never>, with their dependencies acquired when the service's Layer is built, never left in an operation's requirements type, as in Effect<A, E, Database>.
3
3
  ---
4
4
 
5
- ```ts must
5
+ ## Must
6
+
7
+ ```ts
6
8
  class Users extends Context.Service<
7
9
  Users,
8
10
  {
@@ -26,7 +28,9 @@ export const UsersLive = Layer.effect(
26
28
  );
27
29
  ```
28
30
 
29
- ```ts never
31
+ ## Never
32
+
33
+ ```ts
30
34
  class Users extends Context.Service<
31
35
  Users,
32
36
  {
@@ -1,8 +1,11 @@
1
1
  ---
2
2
  description: A test implementation of a service should be built with Layer.sync or Layer.succeed over in-memory state, and should not reach a real database, network, or file. Integration tests that exercise the real service are not in scope.
3
+ tests: only
3
4
  ---
4
5
 
5
- ```ts should
6
+ ## Should
7
+
8
+ ```ts
6
9
  static readonly testLayer = Layer.sync(Cache, () => {
7
10
  const store = new Map<string, string>()
8
11
 
@@ -13,7 +16,9 @@ static readonly testLayer = Layer.sync(Cache, () => {
13
16
  })
14
17
  ```
15
18
 
16
- ```ts should not
19
+ ## Should not
20
+
21
+ ```ts
17
22
  const EmailTest = Layer.effect(
18
23
  Email,
19
24
  Effect.gen(function* () {
@@ -1,8 +1,11 @@
1
1
  ---
2
2
  description: A test that depends on time must use TestClock, never a real sleep or it.live, unless the test needs real time.
3
+ tests: only
3
4
  ---
4
5
 
5
- ```ts must
6
+ ## Must
7
+
8
+ ```ts
6
9
  it.effect("time-based test", () =>
7
10
  Effect.gen(function* () {
8
11
  const fiber = yield* Effect.delay(Effect.succeed("done"), "10 seconds").pipe(Effect.forkChild);
@@ -13,7 +16,9 @@ it.effect("time-based test", () =>
13
16
  );
14
17
  ```
15
18
 
16
- ```ts never
19
+ ## Never
20
+
21
+ ```ts
17
22
  it.live("retries after a delay", () =>
18
23
  Effect.gen(function* () {
19
24
  yield* Effect.sleep("2 seconds");
@@ -58,51 +58,76 @@ threshold: 0.8
58
58
 
59
59
  Optional prose for readers on GitHub. adhere ignores it.
60
60
 
61
- ```ts must
61
+ ## Must
62
+
63
+ ```ts
62
64
  // Correct code lifted from this repo, trimmed to the pattern.
63
65
  ```
64
66
 
65
- ```ts never
67
+ ## Never
68
+
69
+ ```ts
66
70
  // Optional: the incorrect form this rule catches, as it appeared in the repo.
67
71
  ```
68
72
  ````
69
73
 
70
74
  Rules for the rule:
71
75
 
72
- - `description` is one sentence, specific, in the same words as the fences:
76
+ - `description` is one sentence, specific, in the same words as the headings:
73
77
  what code must be, and what it must never be. Jev reads the description
74
78
  with the code, and "must" in both is one demand; vague words ("properly",
75
79
  "correctly") give it nothing to judge.
80
+ - A heading names the code under it only when it is the word alone: `## Never`,
81
+ not `## Never do this`. The next heading at its level or higher ends it.
76
82
  - The `must` block is real code from the repo, not invented, and only correct
77
83
  code: Jev compares files to it, so incorrect code there teaches the wrong
78
- thing. Incorrect code goes in a fence tagged `never`, which Jev reads as what
79
- a violation looks like. Add one whenever violations have a recognizable
84
+ thing. Incorrect code goes under `## Never`, which Jev reads as what a
85
+ violation looks like. Add one whenever violations have a recognizable
80
86
  shape, ideally one found in the repo's history: over the effect preset, a
81
87
  `never` block raised how well Jev told violations from compliant code under
82
88
  every wording tried. A rule with no single correct form can be only a
83
89
  `never` block.
84
90
  - A convention that is a guideline rather than a requirement says "should" in
85
- its description and tags its fences `should` and `should not`. A rule is one
86
- or the other; it cannot mix the two.
91
+ its description and puts its code under `## Should` and `## Should not`. A
92
+ rule is one or the other; it cannot mix the two.
87
93
  - One pattern per file. Two patterns in one `must` block blur the probability.
94
+ - Jev reads code without its comments, except comments that say `@adhere`. A
95
+ rule about comments, such as doc comments on exports, needs
96
+ `includeComments: true` in the config.
97
+ - Rules skip tests: `.test.ts` and `.spec.ts` files, and files under `test/`,
98
+ `tests/`, `__tests__/`, or `fixtures/`. A rule about tests says `tests: only`
99
+ in its front matter, and one that holds in tests as well `tests: include`.
100
+ - A nit, or a rule that tends to flag code wrongly, says `level: warning` in
101
+ its front matter: its findings show in amber and do not fail CI.
88
102
  - `threshold` is optional. Start without it; set it in step 5 if needed.
89
103
 
90
104
  ## 4. Configure
91
105
 
92
106
  Put the config at `.adhere/config.ts`. `adhere.config.ts` and
93
107
  `.adhere.config.ts` also load; keep one. A config is optional when `.adhere/`
94
- exists or a preset is passed on the command line. Use a type-only import so
95
- the file also loads under the native binary:
108
+ exists or a preset is passed on the command line. `adhere init` scaffolds it
109
+ with `defineConfig`, adds `@drkmttr/adhere` as a dev dependency, and writes
110
+ `.adhere/tsconfig.json` so the editor resolves its types; the native binary
111
+ supplies the import either way:
96
112
 
97
113
  ```ts
98
- import type { Config } from "@drkmttr/adhere";
114
+ import { defineConfig } from "@drkmttr/adhere";
99
115
 
100
- export default {
116
+ export default defineConfig({
101
117
  presets: ["effect"],
102
118
  threshold: 0.75,
103
- } satisfies Config;
119
+ exclude: ["**/generated/**"],
120
+ });
104
121
  ```
105
122
 
123
+ To quiet a preset rule that does not suit the repo, override it rather than
124
+ copy it: `overrides: { "alchemy/providers/idempotent-delete": "warning" }`, or
125
+ `"off"`, or `{ threshold: 0.9 }`. The id is the one the report shows.
126
+
127
+ `exclude` lists globs, from the working directory, of files no rule judges.
128
+ Exclude generated and vendored code the skipped directories do not already
129
+ cover, since findings there are noise no one will fix.
130
+
106
131
  Commit `.adhere/cache/` with the code, so the team and CI reuse its judgments
107
132
  instead of paying for them again. Uncached files need a TypeSafe AI API
108
133
  key: one saved by `adhere login`, or `TYPESAFE_API_KEY`, which takes
@@ -127,6 +152,9 @@ Then, per rule, read the findings and decide:
127
152
  rule fires, remove the file. A rule that cannot fire is not a rule.
128
153
  - Hits at 0.9 and above: read them first. They are usually real.
129
154
 
155
+ To verify and fix the findings themselves, one at a time, use the adhere-fix
156
+ skill, which `adhere skill fix` prints.
157
+
130
158
  Threshold changes never re-judge; only edited rules do (per rule) and edited
131
159
  files do (per file). Iterate on wording freely, it costs one request per file
132
160
  per changed rule.