@drkmttr/adhere 0.15.0 → 0.18.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/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.15.0",
3
+ "version": "0.18.0",
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.15.0",
72
- "@drkmttr/adhere-darwin-x64": "0.15.0",
73
- "@drkmttr/adhere-linux-arm64": "0.15.0",
74
- "@drkmttr/adhere-linux-x64": "0.15.0",
75
- "@drkmttr/adhere-win32-x64": "0.15.0"
93
+ "@drkmttr/adhere-darwin-arm64": "0.18.0",
94
+ "@drkmttr/adhere-darwin-x64": "0.18.0",
95
+ "@drkmttr/adhere-linux-arm64": "0.18.0",
96
+ "@drkmttr/adhere-linux-x64": "0.18.0",
97
+ "@drkmttr/adhere-win32-x64": "0.18.0"
76
98
  }
77
99
  }
@@ -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
@@ -37,10 +37,23 @@ wherever this skill says `adhere`, run it the manager's way, as in
37
37
 
38
38
  With pnpm, at a workspace root, add `--ignore-workspace-root-check`.
39
39
 
40
- In a repo without a `package.json`, use the `adhere` on PATH (`adhere
41
- --version`), or **Ask** before installing the executable from the latest
42
- release (`adhere-Windows-x86_64.exe` on Windows), which needs no Node or
43
- package manager:
40
+ Default lint enables `workspacePackages`, `symbols`, and `references`, and
41
+ starts native TypeScript only during execution, after confirmation, when
42
+ pending judgments or cached findings needing location require context. It discovers each
43
+ file's configured project, including package projects under solution roots,
44
+ and falls back to inferred context for unconfigured or excluded files. No root
45
+ `tsconfig.json` or literal root file-list membership is required; do not block
46
+ setup on either. `.adhere/tsconfig.json` only types the config and rules.
47
+ Planning, `--limit 0`, version, help, skills, and validate do not start the
48
+ compiler.
49
+
50
+ In a repo without a `package.json`, inspect any `adhere` on PATH with
51
+ `adhere --version`, then **Ask** whether to use the npm/Bun package launcher
52
+ for the full default or a raw executable with deliberately reduced context.
53
+ The raw release (`adhere-Windows-x86_64.exe` on Windows) needs no Node or
54
+ package manager, but its lint requires explicit config `reads: []` or
55
+ `reads: ["workspacePackages"]`. Do not silently disable readers to make it
56
+ work. Only fetch it after the user chooses that alternative:
44
57
 
45
58
  ```sh
46
59
  curl -fsSL --create-dirs -o ~/.local/bin/adhere \
@@ -48,9 +61,9 @@ curl -fsSL --create-dirs -o ~/.local/bin/adhere \
48
61
  chmod +x ~/.local/bin/adhere
49
62
  ```
50
63
 
51
- Validate and lint need a TypeSafe AI API key: `TYPESAFE_API_KEY`, or one saved
52
- by `adhere login`, which the user runs; never ask for the key in chat or print
53
- it.
64
+ Validate and lint's paid requests need a TypeSafe AI API key: `TYPESAFE_API_KEY`,
65
+ or one saved by `adhere login`, which the user runs; never ask for the key in
66
+ chat or print it. Compiler-free planning with `--limit 0` needs no key.
54
67
 
55
68
  ## 2. Find candidates
56
69
 
@@ -124,7 +137,12 @@ the wording of any. Revise drafts the user wants changed and show them again.
124
137
  - Without a config, run `adhere init`. It also writes two example rules under
125
138
  `.adhere/rules/style/`; delete them unless the user chose them.
126
139
  - Copy each chosen draft to `.adhere/rules/<topic>/<slug>/RULE.md`.
127
- - Add chosen presets, or topics, to `presets` in `.adhere/config.ts`.
140
+ - Add chosen presets, or topics, to `presets` in `.adhere/config.ts`. Presets
141
+ use the audit-wide readers; they do not own or override `reads`.
142
+ - Leave `reads` omitted for the full default. Do not add `reads: []` to the
143
+ normal setup recommendation or as a workaround for missing compiler
144
+ resources. Only set an explicit replacement array if the user chooses to
145
+ disable or limit readers.
128
146
  - Install chosen shared rules: `adhere install org/repo/<id>` for each, or
129
147
  `adhere install org/repo` for all of them. They land in `.adhere/rules/org/repo/`.
130
148
  - Add `exclude` globs for generated or vendored code the default skips miss.
@@ -146,15 +164,115 @@ which to keep, and apply it. Run it again until it reports no contradictions.
146
164
  adhere lint --limit 0
147
165
  ```
148
166
 
149
- This shows the plan and judges nothing: files, rules, requests, and the
150
- estimated cost. Tell the user those numbers. On a large repo, offer to start
151
- with a subtree, such as `--filter 'src/**'`, or a `--limit`. **Ask** before
152
- sending, then run it:
167
+ This shows a compiler-free plan and judges nothing: overall files/rules/cache
168
+ check metrics, and files deferred for a later run. Planning reads/counts/hashes
169
+ own code and checks cached answers; `--limit 0` skips budgeting, native reads,
170
+ and HTTP execution. A positive limit counts files needing requests, not rule
171
+ checks. Only selected files' pending rules and cached-location work are budgeted;
172
+ deferred/waiting counts are files, which can later be found too large. Planning never calls
173
+ `SourceReads` or resolves native symbols/references. Explain the output/prompt
174
+ caveat: estimates omit native context and hook-added data, so actual tokens,
175
+ cost, and request splits may be higher. Source reload/hash work is bounded to
176
+ 8 files; plans retain compact metadata, not all source text. Files are reloaded
177
+ for execution; if normalized code or ignore
178
+ directives changed since planning, rerun rather than using the old plan.
179
+
180
+ Tell the user that compiler type/docs strings and outgoing declaration snippets add
181
+ source/documentation sharing and token cost by default, including complete
182
+ scopes from files outside the audit filter. Whole declaration bodies can use more
183
+ tokens than fixed-line excerpts; budget overflow skips the file instead of
184
+ capping bodies to seven lines or silently truncating them. Compiler-provided
185
+ documentation and JSDoc tags can remain even when ordinary code comments are
186
+ stripped; omitting `symbols` disables type/docs context and omitting
187
+ `references` disables snippets. The global workspace catalog is not sent: only
188
+ this file's matched literal imports appear under canonical package keys.
189
+ Offer an explicit reader subset or code-only opt-out if they want less context;
190
+ never apply one silently. Neither reader selection nor dependency/shared-context
191
+ changes invalidate cached judgments. If they want fresh answers under changed
192
+ context, **Ask** before clearing `.adhere/cache/`; do not clear it automatically.
193
+
194
+ On a large repo, offer a subtree such as `--filter 'src/**'`, or a file cap
195
+ such as `--limit 100`: the first 100 files needing requests in path order, with
196
+ all pending rules per selected file. Judging plus cached-unlocated work in one
197
+ file uses one slot; a cached-locate-only file also counts. Fully cached and
198
+ already-located files report without using slots. After the cap, both judge and
199
+ locate work wait. Known cheap-budget skips refill from later files, but native
200
+ or HTTP-size skips after selection consume a slot without runtime refill.
201
+
202
+ Explain that a file cap is not an HTTP-request or token/cost cap: each file can
203
+ produce judge, locate, split-batch, and private hook requests. **Ask** before
204
+ sending, then run the command below with only the user's chosen filter/file cap:
153
205
 
154
206
  ```sh
155
207
  adhere lint --yes
156
208
  ```
157
209
 
210
+ `SourceReads.prepare(paths)` registers eligible-path metadata only, with no
211
+ file I/O, compiler/SDK import, spawn, snapshot, or semantic query, even for a large list.
212
+ Only after confirmation do pending judgments or cached findings still needing
213
+ location trigger actual per-file native reads just before judge/locate. Native
214
+ transactions are serial through one permit; HTTP execution remains concurrent
215
+ at 8. Returned context is reused locally and full budgets are enforced before
216
+ HTTP, with oversized files skipped and no silent truncation. Shared state plus
217
+ one question must fit 32k tokens and state plus all questions must fit 64k per
218
+ request, with 1k reserved for overhead. The SDK may still parse a full selected
219
+ project, and additional monorepo programs may increase startup time and memory.
220
+
221
+ `symbols` remains `Record<string, string>`: compiler-rendered distinct type
222
+ blocks for same-name binding/narrowing contexts, with compiler documentation
223
+ and JSDoc tags once per canonical symbol per file. It does not promise literal
224
+ IDE labels or selected-call overload presentation. Symbol/type queries are
225
+ batched, and independent SDK requests are chunked to at most 8 within the single
226
+ active native document/snapshot; HTTP work remains concurrent at 8. `references` maps used names to canonical outgoing declarations
227
+ by pathname, selecting the smallest complete AST scope without extra surrounding
228
+ lines. Functions include full bodies; arrow/object initializer variables include
229
+ whole variable statements with `export const` and semicolons. Methods, getters,
230
+ and setters include their whole member, not the entire class; classes,
231
+ interfaces, and type aliases include the whole declaration. Repeated/overlapping
232
+ scopes merge and disjoint scopes join with `…`. This is syntactic scope selection,
233
+ not recursive helper-call expansion: helpers called by an imported function are
234
+ not automatically expanded. Scopes preserve indentation and the comment/directive
235
+ policy, including associated JSDoc when `includeComments` is true; compiler
236
+ docs/tags in `symbols` remain available regardless. Current-file, declaration-file, `node_modules`, and
237
+ outside-root exclusions are unchanged. There are no incoming callers, counts, structured locations,
238
+ unresolved lists, full-file collection, or completeness markers.
239
+
240
+ The first actual read lazily starts one native TypeScript LSP process with its
241
+ public SDK API pipe; both are pinned to exactly `7.1.0-dev.20261009.1` and must
242
+ match. This prerelease contains the tuple-reference serialization fix from
243
+ [microsoft/TypeScript#64080](https://github.com/microsoft/TypeScript/issues/64080). `NativeLsp` owns lifecycle only and makes
244
+ no `textDocument/hover` calls. Each read uses the passed as-written text after
245
+ normalized-code/ignore active-plan validation, opens only that document at
246
+ version 1, uses `textDocument/documentSymbol` as its readiness barrier, and calls
247
+ the public, parameterless `API.getCurrentLanguageServerSnapshot()`.
248
+ Success closes the document, disposes that snapshot under a guarded phase, and
249
+ clears AST caches; native failure closes the session under the watchdog.
250
+
251
+ Keep dependencies/configs stable: only current-read text is pinned, not every
252
+ registered file or an immutable global audit snapshot. Warm compiler project
253
+ graphs can still be large and slow to load; neither total memory nor payload
254
+ size is constant, and contexts may still exceed model budgets.
255
+
256
+ SDK/write phases and LSP requests have 30-second deadlines and reject on compiler
257
+ exit. Queued readers can be interrupted; the active owner is uninterruptible
258
+ for resource safety. A stalled phase is bounded and cleanup adds a 2-second
259
+ `SIGKILL` watchdog. These are phase/cleanup bounds, not a whole-audit timeout.
260
+
261
+ If the user explicitly chose a cache refresh, run from the audited working
262
+ directory, using the repo's package launcher as above:
263
+
264
+ ```sh
265
+ rm -rf .adhere/cache
266
+ adhere lint --limit 0
267
+ ```
268
+
269
+ Show the fresh counts/cache plan; `--limit 0` skips budgeting. For selected
270
+ execution work, explain that its estimate omits native context and may understate
271
+ cost/splits, then **Ask** before paid execution. Deleting the cache removes judgments and linter tallies; it can cause
272
+ new paid requests. Use this explicitly if the user wants to compare fresh
273
+ compiler type/docs and declaration payloads with older cached answers. A change in `B.ts` does not
274
+ otherwise invalidate cached judgments for an unchanged importing `A.ts`.
275
+
158
276
  Walk the user through the report:
159
277
 
160
278
  1. How to read a finding: the rule id, the description, its confidence and
@@ -165,7 +283,8 @@ Walk the user through the report:
165
283
  3. For each noisy rule, propose one change from the adhere skill's "Tuning a
166
284
  rule": narrow the wording, scope it, raise its threshold, make it a
167
285
  warning, or turn it off. **Ask**, apply what the user accepts, and run
168
- lint again. Only the edited rules are judged again.
286
+ lint again. With unchanged own code and model, only the edited rules are
287
+ judged again; dependency/context changes alone are not tracked.
169
288
 
170
289
  CI fails on any error finding, so lint must exit 0 before step 8. **Ask** how
171
290
  to get there: fix the real findings now with the adhere-fix skill
@@ -212,9 +331,16 @@ jobs:
212
331
 
213
332
  Pin the versions the repo already uses: bun's `bun-version`, Node's
214
333
  `node-version` from `.nvmrc` or `engines`, and pnpm's from `packageManager`,
215
- which `pnpm/action-setup` reads. In a repo without a `package.json`, skip the
216
- setup and install, and fetch the executable at the version the user ran
217
- locally:
334
+ which `pnpm/action-setup` reads. Keep optional dependencies enabled so adhere's
335
+ platform package and its compiler dependency are installed. Existing project
336
+ configs and dependencies supply configured context when available; files with
337
+ no configured project or excluded from one use inferred context. Do not require
338
+ or generate a root `tsconfig.json` merely to make default lint run. In a repo
339
+ without a `package.json`, use the package launcher for the full default.
340
+ Only skip setup/install and fetch the raw executable if the user explicitly
341
+ chose and committed `reads: []` or `reads: ["workspacePackages"]` locally.
342
+ That is a reduced-context alternative, not an equivalent default installation.
343
+ Fetch the executable at the version the user ran:
218
344
 
219
345
  ```yaml
220
346
  - run: |