@drkmttr/adhere 0.14.0 → 0.17.5

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 (37) hide show
  1. package/README.md +294 -146
  2. package/bin/adhere.js +19 -4
  3. package/package.json +34 -12
  4. package/presets/effect/config/tests-provide-values-directly/RULE.md +12 -1
  5. package/presets/effect/data/brand-meaningful-primitives/RULE.md +14 -2
  6. package/presets/effect/data/variants-are-tagged-unions/RULE.md +17 -4
  7. package/presets/effect/services/dependencies-through-layers/RULE.md +16 -3
  8. package/presets/effect/services/no-mutable-state/RULE.md +7 -1
  9. package/skills/adhere/SKILL.md +180 -31
  10. package/skills/adhere-setup/SKILL.md +142 -16
  11. package/src/cli.ts +56 -23
  12. package/src/config.ts +49 -16
  13. package/src/index.ts +14 -0
  14. package/src/markdown.ts +9 -5
  15. package/src/providers/cloudflare.ts +84 -0
  16. package/src/providers/jev.ts +66 -0
  17. package/src/providers/openai.ts +89 -0
  18. package/src/providers/post.ts +53 -0
  19. package/src/providers/provider.ts +131 -0
  20. package/src/providers/providers.test.ts +276 -0
  21. package/src/providers/systemOne.ts +95 -0
  22. package/src/services/AdhereConfig.ts +2 -4
  23. package/src/services/Credentials.ts +101 -51
  24. package/src/services/Jev.http.ts +165 -170
  25. package/src/services/Jev.provider.test.ts +207 -0
  26. package/src/services/Jev.reads.test.ts +439 -0
  27. package/src/services/Jev.ts +205 -113
  28. package/src/services/NativeLsp.ts +289 -0
  29. package/src/services/SourceReads.test.ts +1040 -0
  30. package/src/services/SourceReads.ts +437 -0
  31. package/src/services/SourceWalker.ts +26 -59
  32. package/src/workflows/audit.ts +440 -265
  33. package/src/workflows/format.ts +74 -17
  34. package/src/workspaces.test.ts +73 -0
  35. package/src/workspaces.ts +91 -0
  36. package/presets/effect/services/operations-have-no-requirements/RULE.md +0 -50
  37. package/presets/effect/services/test-layers-are-in-memory/RULE.md +0 -29
package/bin/adhere.js CHANGED
@@ -2,7 +2,10 @@
2
2
  /**
3
3
  * `adhere` on PATH. Runs the executable compiled for this machine, which the
4
4
  * install fetched as the optional dependency `@drkmttr/adhere-<platform>-<arch>`
5
- * (see scripts/npm-packages.ts). A checkout has none installed, so there it
5
+ * (see scripts/npm-packages.ts). For semantic reads, passes the native TypeScript
6
+ * compiler package resolved from that executable's dependencies to the bundled
7
+ * SDK. A missing compiler does not block plain commands; the reads adapter
8
+ * validates it when needed. A checkout has no executable installed, so there it
6
9
  * runs src/main.ts with Bun instead.
7
10
  */
8
11
  import { spawnSync } from "node:child_process";
@@ -24,8 +27,20 @@ const resolveExecutable = () => {
24
27
  }
25
28
  };
26
29
 
27
- const run = (command, commandArgs) => {
28
- const result = spawnSync(command, commandArgs, { stdio: "inherit" });
30
+ const resolveCompilerPackage = (prebuilt) => {
31
+ try {
32
+ return createRequire(prebuilt).resolve(`@typescript/typescript-${platform}/package.json`);
33
+ } catch {
34
+ return undefined;
35
+ }
36
+ };
37
+
38
+ const run = (command, commandArgs, compilerPackage) => {
39
+ const env = { ...process.env };
40
+ // This private handoff must not let an inherited value redirect the compiler.
41
+ delete env.ADHERE_TYPESCRIPT_PACKAGE;
42
+ if (compilerPackage) env.ADHERE_TYPESCRIPT_PACKAGE = compilerPackage;
43
+ const result = spawnSync(command, commandArgs, { stdio: "inherit", env });
29
44
  if (result.error) throw result.error;
30
45
  if (result.signal) {
31
46
  // A killed process says nothing itself, so say what killed it.
@@ -39,7 +54,7 @@ const run = (command, commandArgs) => {
39
54
  };
40
55
 
41
56
  const prebuilt = resolveExecutable();
42
- if (prebuilt) run(prebuilt, args);
57
+ if (prebuilt) run(prebuilt, args, resolveCompilerPackage(prebuilt));
43
58
  else if (existsSync(new URL("../.git", import.meta.url)))
44
59
  run("bun", [fileURLToPath(new URL("../src/main.ts", import.meta.url)), ...args]);
45
60
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drkmttr/adhere",
3
- "version": "0.14.0",
3
+ "version": "0.17.5",
4
4
  "private": false,
5
5
  "description": "Lints a repository against the rules in .adhere/, judged by Jev.",
6
6
  "license": "MIT",
@@ -34,7 +34,8 @@
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
- "release": "release-it"
37
+ "release": "release-it",
38
+ "prepare": "vp config"
38
39
  },
39
40
  "dependencies": {
40
41
  "effect": "4.0.0-rc.118"
@@ -44,12 +45,27 @@
44
45
  "@types/bun": "^1.4.2",
45
46
  "@types/node": "^24.13.6",
46
47
  "release-it": "^21.1.0",
47
- "typescript": "7.0.2",
48
- "vite-plus": "0.3.2",
49
- "vitest": "4.1.11"
48
+ "typescript": "7.1.0-dev.20261009.1",
49
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
50
+ "vite-plus": "1.1.0",
51
+ "vitest": "5.0.3"
50
52
  },
51
53
  "overrides": {
52
- "@effect/platform-node-shared": "4.0.0-rc.118"
54
+ "@effect/platform-node-shared": "4.0.0-rc.118",
55
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
56
+ "vitest": "5.0.3"
57
+ },
58
+ "devEngines": {
59
+ "packageManager": {
60
+ "name": "bun",
61
+ "version": "1.4.2",
62
+ "onFail": "download"
63
+ },
64
+ "runtime": {
65
+ "name": "node",
66
+ "version": "24.21.0",
67
+ "onFail": "download"
68
+ }
53
69
  },
54
70
  "release-it": {
55
71
  "$schema": "https://unpkg.com/release-it@21.1.0/schema/release-it.json",
@@ -64,14 +80,20 @@
64
80
  },
65
81
  "npm": {
66
82
  "allowSameVersion": true,
67
- "publish": false
83
+ "publish": false,
84
+ "versionArgs": [
85
+ "--force"
86
+ ]
68
87
  }
69
88
  },
89
+ "versionArgs": [
90
+ "--force"
91
+ ],
70
92
  "optionalDependencies": {
71
- "@drkmttr/adhere-darwin-arm64": "0.14.0",
72
- "@drkmttr/adhere-darwin-x64": "0.14.0",
73
- "@drkmttr/adhere-linux-arm64": "0.14.0",
74
- "@drkmttr/adhere-linux-x64": "0.14.0",
75
- "@drkmttr/adhere-win32-x64": "0.14.0"
93
+ "@drkmttr/adhere-darwin-arm64": "0.17.5",
94
+ "@drkmttr/adhere-darwin-x64": "0.17.5",
95
+ "@drkmttr/adhere-linux-arm64": "0.17.5",
96
+ "@drkmttr/adhere-linux-x64": "0.17.5",
97
+ "@drkmttr/adhere-win32-x64": "0.17.5"
76
98
  }
77
99
  }
@@ -1,8 +1,19 @@
1
1
  ---
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.
2
+ description: A test of code that consumes configuration should supply it with Layer.succeed on the config service, and should not set environment variables or a ConfigProvider to do it.
3
3
  tests: only
4
4
  ---
5
5
 
6
+ Code that reads its settings from the environment in a test reads them from
7
+ the environment in production too, so a test that sets them hides that the
8
+ code skips the config service.
9
+
10
+ A test whose subject is how configuration is read, such as its decoding,
11
+ defaults, precedence, or fallback to the environment, has to supply raw
12
+ inputs and is out of scope. So are values a test plants to check that they
13
+ never leak, and the bindings of a separately deployed Worker. A test or
14
+ fixture that sets the environment so the code under test reads its settings
15
+ from there is in scope, even in a file that also tests a loader.
16
+
6
17
  ## Should
7
18
 
8
19
  ```ts
@@ -1,7 +1,19 @@
1
1
  ---
2
- description: A primitive with semantic meaning, such as an id, email, URL, port, or count, must be declared as a branded schema, never as a bare string or number. The rule judges where a type is declared, such as a schema field, an interface, or a type alias; a literal value, local variable, or parameter whose type is declared elsewhere is out of scope.
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
3
  ---
4
4
 
5
+ Two ids of different things are both strings, so nothing stops one being
6
+ passed where the other belongs. A brand makes that mix-up a type error where
7
+ the value crosses from one module to another.
8
+
9
+ The rule judges where a domain type is declared: a schema field, interface, or
10
+ type alias for what services exchange, return, or store. A shape that mirrors
11
+ an external payload or a table row until it is mapped, a view's props or view
12
+ model, and a literal, local variable, or parameter whose type is declared
13
+ elsewhere are not domain types. A type the code's own modules pass between
14
+ them, such as a reference to a thread, an issue, or a delivery, is a domain
15
+ type even in a file that also mirrors a payload.
16
+
5
17
  ## Must
6
18
 
7
19
  ```ts
@@ -25,6 +37,6 @@ type AccountId = string;
25
37
 
26
38
  const Session = Schema.Struct({
27
39
  accountId: Schema.String,
28
- retries: Schema.Number,
40
+ email: Schema.String,
29
41
  });
30
42
  ```
@@ -1,7 +1,19 @@
1
1
  ---
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. The rule judges declared union types and schemas; an object literal with a tag field is out of scope, and so is a type that mirrors a third-party format whose tag key that format fixes, such as the type of a Slack Block Kit block.
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
3
  ---
4
4
 
5
+ A tagged schema decodes each variant at the boundary and lets `Match.tag`
6
+ check every case, where a hand-written union is neither validated nor checked
7
+ for a case added later. Simple alternatives without fields may be
8
+ Schema.Literals.
9
+
10
+ The rule judges declared union types. A union used only inside one module,
11
+ such as a view's state, a reducer's actions, or a function's own result, is
12
+ not a domain type, and neither is a generic type, an object literal with a tag
13
+ field, or a type that mirrors a third-party format whose tag key that format
14
+ fixes, such as a Slack Block Kit block. A union that a service returns, or an
15
+ event a module emits, is in scope even in a file that declares nothing else.
16
+
5
17
  ## Must
6
18
 
7
19
  ```ts
@@ -27,8 +39,9 @@ const renderResult = (result: Result) =>
27
39
  ## Never
28
40
 
29
41
  ```ts
30
- type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
42
+ type Payment = { kind: "card"; last4: string } | { kind: "invoice"; dueDate: string };
31
43
 
32
- const area = (shape: Shape) =>
33
- shape.kind === "circle" ? Math.PI * shape.radius ** 2 : shape.side ** 2;
44
+ interface Payments {
45
+ readonly find: (id: PaymentId) => Effect.Effect<Payment, PaymentNotFound>;
46
+ }
34
47
  ```
@@ -1,7 +1,17 @@
1
1
  ---
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 and returns the service's operations with them built in. A helper that takes a client and one call's arguments and returns a single Effect is not a factory and is out of scope; the code that assembles the service from such helpers is in scope.
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 function or class that takes other services' implementations as arguments and returns a service's implementation for its callers to wire by hand.
3
3
  ---
4
4
 
5
+ A service wired by hand hides its dependencies from the types, so each caller
6
+ builds them again, and a test cannot replace one with a layer.
7
+
8
+ A function that the service's own Layer calls with the services it yielded is
9
+ part of that Layer and is out of scope, and so is a decorator that takes a
10
+ service and returns the same service. A boundary adapter that wraps a
11
+ library's client, such as an SDK or a Promise API, takes no service and is out
12
+ of scope. A function that builds a service from other services is in scope
13
+ when code other than that service's Layer calls it.
14
+
5
15
  ## Must
6
16
 
7
17
  ```ts
@@ -31,8 +41,11 @@ export const UsersLive = Layer.effect(
31
41
  ## Never
32
42
 
33
43
  ```ts
34
- export const makeUsers = (database: Database["Service"], logger: Logger["Service"]) => ({
35
- find: (id: UserId) =>
44
+ export const makeUsers = (
45
+ database: Database["Service"],
46
+ logger: Logger["Service"],
47
+ ): Users["Service"] => ({
48
+ find: (id) =>
36
49
  Effect.gen(function* () {
37
50
  yield* logger.log(`finding ${id}`);
38
51
  return yield* database.findUser(id);
@@ -1,7 +1,13 @@
1
1
  ---
2
- description: A service must expose only readonly members, never mutable state.
2
+ description: The interface a Context.Service exposes must hold only readonly members, never writable fields or mutable state, such as a Map, Set, or array, that its consumers could change, even through a readonly property.
3
3
  ---
4
4
 
5
+ State the service's implementation keeps behind its operations is not part of
6
+ its interface, and neither are a fixture's controls kept beside the service it
7
+ provides. A plain interface that no Context.Service declares, such as a
8
+ helper's registry or a connection's data, is out of scope. A Context.Service's
9
+ interface is in scope however its file declares it.
10
+
5
11
  ## Must
6
12
 
7
13
  ```ts
@@ -24,7 +24,8 @@ adhere init --shared org/repo # a repo of rules other repos install, with CI
24
24
  adhere login # save a TypeSafe AI API key; TYPESAFE_API_KEY takes precedence
25
25
  adhere validate # rule wording, rules a linter could check, contradictions
26
26
  adhere lint # judge the working directory; exit 1 on an error finding
27
- adhere lint --limit 0 # show the plan and its cost, judge nothing
27
+ adhere lint --limit 0 # compiler-free counts/cache plan; no budgeting
28
+ adhere lint --limit 100 # first 100 files needing requests; all pending rules per file
28
29
  adhere lint --yes # send without asking, as in CI
29
30
  adhere lint --preset effect # add a built-in rule set; the config becomes optional
30
31
  adhere lint --filter 'src/**' # read only matching files; repeat, and ! to leave out
@@ -35,7 +36,20 @@ adhere install org/repo[/topic[/rule]][#ref] # copy them into .adhere/rules/or
35
36
  adhere skill [docs|setup|fix] # print a skill
36
37
  ```
37
38
 
38
- `adhere <command> --help` lists every flag.
39
+ `adhere <command> --help` lists every flag. Version, help, skills, `validate`,
40
+ and planning with `--limit 0` do not start the native compiler. Default `lint`
41
+ starts it only during execution, after confirmation, for pending judgments or
42
+ cached findings still needing location.
43
+
44
+ `--limit` now counts files, not rule checks; use the same flag without an extra
45
+ unit selector. Each request-bearing file consumes one slot for all its pending
46
+ rules and cached-location work. Cached-locate-only files count; fully cached and
47
+ already-located files report without consuming slots. After the cap, both judge
48
+ and locate work wait. Known cheap-budget skips refill from later paths; native
49
+ or HTTP-size skips after selection consume a slot without runtime refill.
50
+ A file can generate judge, locate, split-batch, and private hook requests, so
51
+ this is not an HTTP-request or cost cap. Confirm the estimated cost and its
52
+ native-context caveat before execution.
39
53
 
40
54
  ## Rule files
41
55
 
@@ -106,28 +120,21 @@ Front matter besides `description`, all optional:
106
120
  one line, scope a rule without widening its description: a finding stands
107
121
  only when Jev says the code that breaks the rule is as every `appliesTo`
108
122
  describes and as no `excludeIf` does.
109
- - `reads: ["workspacePackages"]`, a JSON array on one line, names what Jev
110
- reads for the rule beside the code, each under that name. There is one so
111
- far, `workspacePackages`: each of the repository's own packages, by name,
112
- with its directory, read from the root `package.json`'s `workspaces` or
113
- `pnpm-workspace.yaml`. Use it for a rule that turns on whether an import is
114
- of the repository's own package or an installed one, and name
115
- `workspacePackages` in the description. A name adhere does not have refuses
116
- the run. The rule costs a request of its own per file, and a changed
117
- workspace judges it again.
118
123
 
119
124
  A rule's directory can hold a `RULE.ts` instead, which default-exports
120
125
  `defineRule({...})` from `@drkmttr/adhere`, with the fields a config's inline
121
126
  rule takes:
122
127
  `description`, `must`, `never`, and the rest. Its `appendState(state, file, Bun)`
123
128
  runs right before each request for the rule, and what it returns is spread
124
- over the request's state, `{ code: { "1": "…", "2": "…" } }`, the file's
125
- sections as Jev reads them. Use it to give Jev what the file cannot show, such
129
+ over the request's state: `code`, the file's sections as Jev reads them,
130
+ plus the shared context selected by the config's `reads`. Use it to give Jev what the file cannot show, such
126
131
  as a schema another file holds, and name the key it adds in the description.
127
132
  Nothing it returns is checked, so it can break its own rule. The rule gets
128
- requests of its own, one more per file judged, and its judgments are not
129
- redone when what the hook reads changes, only when the file or the rule,
130
- hook included, does. Helpers it imports go beside it in its directory.
133
+ requests of its own, repeating the shared context. Hook-owned external data
134
+ changes are not tracked by the cache; neither are built-in read-context contents
135
+ or reader selection. Judgments are redone when own code, model, rule, matcher,
136
+ or hook source changes, or the user clears the cache. Helpers it imports go
137
+ beside it in its directory.
131
138
 
132
139
  A rule a regex, an import check, or the type checker could flag every time
133
140
  belongs in that tool, not adhere. `adhere validate` reports rules that look
@@ -145,7 +152,8 @@ import { defineConfig } from "@drkmttr/adhere";
145
152
  export default defineConfig({
146
153
  presets: ["typescript", "security"], // built-in rule sets, or topics such as "effect/basics"
147
154
  threshold: 0.8, // default 0.8
148
- sufficiencyThreshold: 0.6, // below it, a finding warns the file may not show enough
155
+ sufficiencyThreshold: 0.6, // below it, a finding warns the file and shared context may not show enough
156
+ reads: ["workspacePackages", "symbols", "references"], // default; omitting this field keeps all three
149
157
  exclude: ["**/generated/**"], // files no rule judges
150
158
  includeComments: false, // true only for rules about comments
151
159
  overrides: {
@@ -155,6 +163,108 @@ export default defineConfig({
155
163
  });
156
164
  ```
157
165
 
166
+ Top-level `reads` selects shared context for every audited file and all its
167
+ rules, including presets. `resolveConfig` defaults to
168
+ `["workspacePackages", "symbols", "references"]` when the field is omitted,
169
+ even without a config file. An explicit array replaces the default: `reads: []`
170
+ is the code-only opt-out, and a subset limits context to those readers. Presets
171
+ do not own or override this selection. Rule-level `reads`, in Markdown front
172
+ matter, `defineRule`, or inline rules, is rejected with guidance to move it to
173
+ the config. The available names are:
174
+
175
+ - `workspacePackages`: only packages matched by this file's literal imports,
176
+ as `{ canonicalPackageName: directory }`. Subpaths match the owning package;
177
+ the discovered workspace catalog is internal and never sent. The cheap lexer
178
+ recognizes static/type imports, re-exports, dynamic import and require calls,
179
+ including plain literal templates in calls. Computed modules/template
180
+ substitutions are skipped and `require` binding is not resolved: this is
181
+ cheap filtering, not semantic module resolution. No matches yields `{}`.
182
+ - `symbols`: `Record<string, string>`, mapping names to compiler-rendered type
183
+ blocks plus compiler documentation and JSDoc tags. Distinct same-name binding
184
+ and narrowing types stay distinct; docs/tags appear once per canonical symbol
185
+ per file. This is not literal IDE hover text: there are no IDE labels or
186
+ selected-call overload presentation. Unused local declarations may have type
187
+ blocks too. Library types/docs can appear even when their source is excluded
188
+ from snippets.
189
+ - `references`: `{ Name: { pathname: declarationSnippet } }` for used symbols'
190
+ canonical outgoing alias-target declarations. Each snippet is the smallest
191
+ complete AST declaration scope, with no extra surrounding lines: full function
192
+ bodies; whole arrow/object initializer variable statements, including
193
+ `export const` and semicolons; whole methods/getters/setters, not their classes;
194
+ and whole class/interface/type-alias declarations. Repeated/overlapping scopes
195
+ merge; disjoint scopes join with `…`. This is syntactic selection, not recursive
196
+ helper-call expansion: an imported function's called helpers are not added
197
+ automatically. Indentation and comment/directive policy remain; associated
198
+ JSDoc stays when `includeComments` is true; compiler docs/tags in `symbols`
199
+ remain available regardless.
200
+ Current-file, `.d.ts`, `.d.mts`, `.d.cts`, `node_modules`, and outside-root
201
+ exclusions remain. There are no counts, incoming callers, structured locations,
202
+ unresolved lists, full-file collection, or completeness metadata.
203
+
204
+ Default npm-installed lint uses `symbols` and `references` with adhere's pinned
205
+ native TypeScript compiler through the npm/Bun launcher. No root `tsconfig.json`
206
+ or root file-list membership is required. During execution, native TypeScript
207
+ opens files with pending judgments or cached findings needing location and
208
+ discovers each file's configured project, including package
209
+ projects under solution roots. Unconfigured or excluded files fall back to
210
+ best-effort inferred context, not a refusal. These are complete outgoing AST
211
+ declaration scopes and compiler type/docs strings, not a complete
212
+ workspace-reference graph.
213
+
214
+ `SourceReads.prepare(paths)` registers metadata only: no file I/O, compiler/SDK
215
+ import, spawn, snapshot, or semantic query, regardless of how many paths are registered.
216
+ The first real read lazily starts one TypeScript `--lsp --stdio` process,
217
+ pinned with its SDK to exactly `7.1.0-dev.20261009.1`. This prerelease contains
218
+ the tuple-reference serialization fix from [microsoft/TypeScript#64080](https://github.com/microsoft/TypeScript/issues/64080);
219
+ the SDK and native executable must match that exact version. `NativeLsp` owns lifecycle only; it makes no `textDocument/hover` calls.
220
+ Types and documentation come directly from the compiler SDK through the public
221
+ API attached with `API.fromLSPConnection` to that process's API pipe. Symbol/type
222
+ queries are batched; independent SDK requests run in chunks of at most 8 within
223
+ the current document transaction.
224
+
225
+ Only one native transaction owns a permit at a time. It receives the as-written
226
+ text after normalized-code/ignore active-plan validation, opens the current
227
+ document with `didOpen` version 1, uses `textDocument/documentSymbol` as the
228
+ readiness barrier, and calls
229
+ the public, parameterless `API.getCurrentLanguageServerSnapshot()` per read. Success sends `didClose`, performs
230
+ guarded snapshot disposal, and clears the AST cache; native failure closes the
231
+ session under a watchdog. There is no audit-wide immutable snapshot and no
232
+ preload/freeze of registered files. Only current-read text is pinned; keep live
233
+ dependencies/configs stable during the audit.
234
+
235
+ SDK/write phases and LSP requests have 30-second deadlines and reject on compiler
236
+ exit. Waiting readers are interruptible; the active owner is uninterruptible
237
+ for resource safety. A stalled phase is bounded, with a further 2-second cleanup
238
+ watchdog escalating to `SIGKILL`; this is not a total audit-duration guarantee.
239
+ Native context is serial, but HTTP execution retains concurrency 8.
240
+
241
+ Even one pending file may require parsing its selected project; warm monorepo
242
+ program graphs can still consume substantial time/memory. Bounding active
243
+ documents/snapshots does not make total memory or model payload size constant,
244
+ and oversized contexts are still possible. Context is queried just
245
+ before judge/locate and reused locally. Planning never calls `SourceReads`.
246
+ Source checkouts use their development compiler dependency. Raw GitHub
247
+ binaries still have no compiler resources: their lint requires explicit config
248
+ `reads: []` or `reads: ["workspacePackages"]` for execution; use the package
249
+ launcher for the full default. Plan-only `--limit 0` needs no compiler or API key.
250
+
251
+ Rules without `appendState` share requests and pay for shared input context
252
+ once per request, not once per rule. Context repeats on request splitting,
253
+ locating findings, and hook requests. Compiler documentation and complete declaration
254
+ scopes add default token/privacy cost, including source from files not judged.
255
+ Complete scopes may contain substantially more source/tokens than fixed-line
256
+ excerpts; function bodies are not peeked or capped to seven lines.
257
+ Compiler-provided docs and JSDoc tags can remain present with ordinary comments
258
+ stripped. Omit `symbols` to disable type/docs context, `references` to disable
259
+ snippets, or use `reads: []` for code-only built-in context.
260
+ Plan token/cost estimates include code and cheap workspace metadata only, not
261
+ native context or hook-added data. Actual cost and request splits may therefore
262
+ be higher. Shared state plus any one question must fit 32k tokens, and state
263
+ plus all questions in a request must fit 64k, with 1k reserved for overhead.
264
+ Execution splits question batches, checks full budgets before HTTP, and skips
265
+ oversized files instead of silently truncating type/docs strings or complete scopes. Only
266
+ `appendState` forces a private rule request group.
267
+
158
268
  `overrides` changes any rule by the id the report shows, a preset's with the
159
269
  preset first, without copying it; an id no rule has refuses the run. `rules`
160
270
  in the config, inline or as a directory of rule directories, replaces
@@ -189,10 +299,12 @@ so a private repo needs git's credentials for GitHub.
189
299
 
190
300
  ## Comments and suppressions
191
301
 
192
- Jev never sees comments: adhere blanks them before judging, since comments
193
- claiming code is safe hid real violations in the evals. A comment containing
194
- `@adhere` is a note Jev does read: use it for a checked fact the code relies
195
- on that the file cannot show, with how you know.
302
+ adhere blanks ordinary comments in judged code and declaration snippets, since
303
+ comments claiming code is safe hid real violations in the evals. Compiler
304
+ documentation and JSDoc tags in `symbols` are separate and can remain even with
305
+ `includeComments: false`. A comment containing `@adhere` is a note Jev does read:
306
+ use it for a checked fact the code relies on that the file cannot show, with
307
+ how you know.
196
308
 
197
309
  ```ts
198
310
  // @adhere DeleteActivity succeeds on a missing activity; probed 2026-09-25.
@@ -210,20 +322,56 @@ file. Name several rules with commas.
210
322
 
211
323
  ## Cache and API key
212
324
 
213
- Judgments are cached in `.adhere/cache/`, keyed by each file's content without
214
- comments and by the rule's text. Commit it: every judgment is a paid request,
215
- and a committed cache gives the team and CI the same findings without paying
216
- again. A changed file re-judges every rule for it; an edited rule re-judges
217
- only that rule; a threshold change re-judges nothing. A run where everything
218
- is cached needs no key or network. Mark it generated to keep it out of diffs:
325
+ Judgments are cached in `.adhere/cache/`, keyed by each file's normalized own code and
326
+ model/rule/matcher/hook-source fingerprints. Commit it: judging uses paid
327
+ requests, and a committed cache gives the team and CI the same findings without
328
+ paying again. Changed own code re-judges every rule for that file; an edited rule
329
+ re-judges that rule; a threshold change re-judges nothing.
330
+
331
+ Dependencies, related source, reader selection, shared read-context contents,
332
+ and hook-owned external data are deliberately not fingerprinted. If `A.ts` imports
333
+ `B.ts`, changing `B.ts` does not invalidate `A.ts`. Changing config `reads` does
334
+ not refresh its cached answers either. This keeps planning cheap but accepts
335
+ stale dependency/context judgments.
336
+
337
+ `planAudit` reads/counts/hashes code, checks the cache, and makes code plus cheap
338
+ workspace estimates without compiler or `SourceReads` calls. It applies `--limit`
339
+ to request-bearing files in path order before budgeting all pending rules and
340
+ cached-location work for each selected file. Rule/cache check totals remain
341
+ overall metrics; deferred/waiting counts are files. Deferred files remain
342
+ unbudgeted until selected and can later be found too large. `--limit 0` skips
343
+ budgeting, native reads, and HTTP execution.
344
+ Source reload/hash work is bounded to 8 files; plans retain compact metadata and
345
+ estimates, not all file contents. Only sampled-linter files need a planning
346
+ reread. After confirmation, execution reloads request-bearing files and cached
347
+ finding excerpts, preparing full native context only for pending or
348
+ cached-unlocated files just before judge/locate. Every reload validates normalized
349
+ code and ignore directives against the active plan; changes refuse with rerun
350
+ guidance, without adding persistent-cache keys. A fully cached and located run
351
+ starts no compiler and needs no API key or Jev requests.
352
+
353
+ For an explicit refresh, from the audited working directory:
354
+
355
+ ```sh
356
+ rm -rf .adhere/cache
357
+ adhere lint --limit 0 # compiler-free counts/cache plan; budgeting is skipped
358
+ adhere lint # confirm fresh execution; actual context costs may be higher
359
+ ```
360
+
361
+ This removes judgments and linter tallies and can cause new paid requests. Do
362
+ not clear it automatically when dependencies or readers change. Clear explicitly
363
+ if the user wants to compare fresh compiler type/docs and declaration payloads rather than cached
364
+ answers from an older format. Mark it generated to keep it out of diffs:
219
365
 
220
366
  ```text
221
367
  .adhere/cache/** linguist-generated -diff
222
368
  ```
223
369
 
224
370
  The key comes from `TYPESAFE_API_KEY` or from `adhere login`. lint prints a
225
- plan with the request count and estimated cost before sending, and asks at a
226
- terminal.
371
+ compiler-free plan with estimated requests/cost before sending and asks at a
372
+ terminal. Output and prompt warn that native context is omitted and may increase
373
+ actual tokens, cost, and request splits. Only confirmed execution prepares context
374
+ and enforces full budgets before HTTP.
227
375
 
228
376
  ## Reading a report
229
377
 
@@ -241,8 +389,9 @@ line; `×` is an error, `⚠` a warning. `confidence` is Jev's probability that
241
389
  the file breaks the rule. On a terminal it is amber near its rule's
242
390
  threshold, within a quarter of the room above it (0.80 to 0.84 at a threshold
243
391
  of 0.80), and green above that: a rule whose findings are mostly amber turns
244
- on where its threshold sits. `context` is its probability that the file shows
245
- enough to decide, colored the same way around the sufficiency threshold, 0.6
392
+ on where its threshold sits. `context` is its probability that the file and
393
+ shared context show enough to decide; all three readers are included by default.
394
+ It is colored the same way around the sufficiency threshold, 0.6
246
395
  by default: amber from 0.50 to 0.69, red below. Below 0.6 the finding also
247
396
  carries `warning: this file may not show enough to check this rule`. It turns
248
397
  on something outside the file, and was usually false in the evals: check that