@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/README.md +313 -131
- package/bin/adhere.js +19 -4
- package/package.json +34 -12
- package/skills/adhere/SKILL.md +180 -31
- package/skills/adhere-setup/SKILL.md +142 -16
- package/src/cli.ts +56 -23
- package/src/config.ts +62 -16
- package/src/index.ts +14 -0
- package/src/markdown.ts +9 -5
- package/src/providers/cloudflare.ts +84 -0
- package/src/providers/jev.ts +66 -0
- package/src/providers/openai.ts +89 -0
- package/src/providers/post.ts +53 -0
- package/src/providers/provider.ts +131 -0
- package/src/providers/providers.test.ts +276 -0
- package/src/providers/systemOne.ts +95 -0
- package/src/services/AdhereConfig.ts +2 -4
- package/src/services/Credentials.ts +101 -51
- package/src/services/Jev.http.ts +184 -171
- package/src/services/Jev.provider.test.ts +483 -0
- package/src/services/Jev.reads.test.ts +516 -0
- package/src/services/Jev.ts +276 -120
- package/src/services/NativeLsp.ts +289 -0
- package/src/services/SourceReads.test.ts +1289 -0
- package/src/services/SourceReads.ts +460 -0
- package/src/services/SourceWalker.ts +26 -59
- package/src/workflows/audit.ts +513 -267
- package/src/workflows/format.ts +74 -17
- package/src/workspaces.test.ts +73 -0
- package/src/workspaces.ts +91 -0
package/README.md
CHANGED
|
@@ -69,11 +69,13 @@ Here's a demo of what the output looks like.
|
|
|
69
69
|
<img alt="demo" align="center" src="https://github.com/darkmatter/adhere/raw/main/eval/demo.gif" />
|
|
70
70
|
</details>
|
|
71
71
|
|
|
72
|
-
Presets for TypeScript, React, security, Effect, and alchemy are included
|
|
73
|
-
|
|
72
|
+
Presets for TypeScript, React, security, Effect, and alchemy are included.
|
|
73
|
+
Run them to quickly adhere your agent to recommended rules.
|
|
74
74
|
|
|
75
75
|
```sh
|
|
76
|
-
|
|
76
|
+
bun add -g @drkmttr/adhere
|
|
77
|
+
adhere login # save your typesafe api key
|
|
78
|
+
adhere lint --preset typescript,react
|
|
77
79
|
```
|
|
78
80
|
|
|
79
81
|
Rules a normal linter can check exactly, such as a banned import or a type
|
|
@@ -106,24 +108,33 @@ your rules.
|
|
|
106
108
|
|
|
107
109
|
## Install
|
|
108
110
|
|
|
111
|
+
Add adhere to your repo's dev dependencies, and run it with `npx adhere` or
|
|
112
|
+
`bunx adhere`:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npm install -D @drkmttr/adhere
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Or download the standalone executable, which needs no Node or Bun:
|
|
119
|
+
|
|
109
120
|
```sh
|
|
110
121
|
curl -fsSL --create-dirs -o ~/.local/bin/adhere \
|
|
111
122
|
https://github.com/darkmatter/adhere/releases/latest/download/adhere-$(uname -s)-$(uname -m)
|
|
112
123
|
chmod +x ~/.local/bin/adhere
|
|
113
124
|
```
|
|
114
125
|
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
- The npm package brings its own TypeScript compiler, for the
|
|
127
|
+
[`symbols` and `references`](#what-jev-reads) Jev reads by default. No other
|
|
128
|
+
setup is needed.
|
|
129
|
+
- The standalone executable has no compiler, so it lints only with `reads: []`
|
|
130
|
+
or `reads: ["workspacePackages"]` in the [config](#config). It suits a global
|
|
131
|
+
install that your agent config can always call.
|
|
132
|
+
- It is built for macOS and Linux, on arm64 and x64, and for Windows x64:
|
|
133
|
+
download `adhere-Windows-x86_64.exe` from the
|
|
119
134
|
[latest release](https://github.com/darkmatter/adhere/releases/latest).
|
|
120
|
-
-
|
|
121
|
-
`
|
|
122
|
-
|
|
123
|
-
`@drkmttr/adhere` as a dev dependency and run `npx adhere`. That `adhere`
|
|
124
|
-
starts through Node, or through Bun under `bunx`.
|
|
125
|
-
- adhere sends each file it judges to Jev at `api.typesafe.ai`, authenticated
|
|
126
|
-
with a TypeSafe AI API key.
|
|
135
|
+
- adhere sends each file it judges, with what it [reads](#what-jev-reads)
|
|
136
|
+
beside it, to Jev at `api.typesafe.ai`, authenticated with a TypeSafe AI API
|
|
137
|
+
key.
|
|
127
138
|
|
|
128
139
|
### Login
|
|
129
140
|
|
|
@@ -131,23 +142,27 @@ chmod +x ~/.local/bin/adhere
|
|
|
131
142
|
adhere login # prompts for the key, masking what you type
|
|
132
143
|
adhere login < key.txt # reads it from stdin instead
|
|
133
144
|
adhere logout # deletes the saved key
|
|
145
|
+
adhere login --openai # saves an OpenAI API key, for the openai provider
|
|
146
|
+
adhere logout --openai # deletes it, and only it
|
|
134
147
|
```
|
|
135
148
|
|
|
136
149
|
- The key is saved to `~/.config/adhere/credentials.json`, or under
|
|
137
|
-
`$XDG_CONFIG_HOME` when that is set, readable only by you.
|
|
150
|
+
`$XDG_CONFIG_HOME` when that is set, readable only by you. An OpenAI key is
|
|
151
|
+
saved beside the TypeSafe AI one, for a config whose provider is
|
|
152
|
+
`openai()`; see [Providers](#providers).
|
|
138
153
|
- `TYPESAFE_API_KEY`, when set, takes precedence over the saved key, so CI can
|
|
139
|
-
pass a key without a login.
|
|
140
|
-
- The key is read only when a request is about to be sent. A run
|
|
141
|
-
|
|
154
|
+
pass a key without a login. `OPENAI_API_KEY` does the same for OpenAI's.
|
|
155
|
+
- The key is read only when a request is about to be sent. A run the cache
|
|
156
|
+
answers in full needs no key and no network.
|
|
142
157
|
|
|
143
158
|
## Quick start
|
|
144
159
|
|
|
145
160
|
```sh
|
|
146
|
-
adhere init # .adhere/config.ts, two example rules, and the dependency
|
|
147
|
-
adhere login # save your TypeSafe AI API key, once
|
|
148
|
-
adhere validate # check your rules' wording, and ask Jev whether any contradict
|
|
149
|
-
adhere lint # audit the working directory
|
|
150
|
-
git add .adhere
|
|
161
|
+
npx adhere init # .adhere/config.ts, two example rules, and the dependency
|
|
162
|
+
npx adhere login # save your TypeSafe AI API key, once
|
|
163
|
+
npx adhere validate # check your rules' wording, and ask Jev whether any contradict
|
|
164
|
+
npx adhere lint # audit the working directory
|
|
165
|
+
git add .adhere # commit the rules, and the judgments they cost
|
|
151
166
|
```
|
|
152
167
|
|
|
153
168
|
### Init
|
|
@@ -294,9 +309,8 @@ rule and the changed one side by side, and compare what each flags.
|
|
|
294
309
|
amber and never fail the run, so the experiment can sit in the repo, and in
|
|
295
310
|
CI, while you watch it.
|
|
296
311
|
3. Change the copy: its description, its examples, its `appliesTo` or
|
|
297
|
-
`excludeIf
|
|
298
|
-
|
|
299
|
-
[`appendState`](#rules-as-typescript-files) hook.
|
|
312
|
+
`excludeIf`. To try giving Jev more to read, make the copy a `RULE.ts` with
|
|
313
|
+
an [`appendState`](#rules-as-typescript-files) hook.
|
|
300
314
|
4. Run `adhere lint`, or `adhere lint --filter '<glob>'` to try it on part of
|
|
301
315
|
the repo first, and compare the two rules' findings on the same files.
|
|
302
316
|
|
|
@@ -340,13 +354,13 @@ writing a rule.
|
|
|
340
354
|
|
|
341
355
|
### What else the studies found
|
|
342
356
|
|
|
343
|
-
| Lesson | What the study found
|
|
344
|
-
| ------------------------------------------------------ |
|
|
345
|
-
| "Should" is for people, not a weaker check. | Rules written with "should" and with "must" scored nearly the same ([study](eval/studies/rule-vocabulary.md)).
|
|
346
|
-
| A wrong finding is usually the rule's fault. | Above 0.8, 31 of 35 wrong findings repeated within a rule: a scope its words reach past, or the same misreading again. Fixing the wording fixes them together. Below 0.8, one-off misreads grow, to about one finding in six at 0.6 to 0.7 ([study](eval/studies/presets.md)).
|
|
347
|
-
| Narrow a rule in its description. | Jev scores an `excludeIf` near 0.5 for real and false findings alike, so it thins a rule's findings about as much as a higher threshold ([study](eval/studies/presets.md)). Narrowing the description moves the judgment itself: scoping alchemy's idempotent-delete rule to providers' delete handlers dropped its findings elsewhere from 0.67–0.88 to 0.08–0.21, with nothing real lost ([study](eval/studies/idempotent-delete.md)).
|
|
348
|
-
| Beware rules that turn on what the file does not show. | The weakest preset rules hinged on facts outside the file: whether a client has a default timeout, whether a script is an entry point, whether a key is public ([study](eval/studies/presets.md)). Warning below a context of 0.7 marked one finding in five, and those were real about half the time, where the rest were real three times in four ([study](eval/studies/sufficiency.md)). Make such a rule a warning, or give Jev the fact with [`reads`](#what-
|
|
349
|
-
| Jev believes comments. | Eight of nine real violations of the idempotent-delete rule carried a comment wrongly saying they were fine, and scored 0.31 to 0.62; without comments, all nine scored 0.81 or more ([study](eval/studies/idempotent-delete.md)). That is why adhere takes comments out. A fact Jev needs, such as an exemption, goes in an `@adhere` note, which stays ([study](eval/studies/comments.md)).
|
|
357
|
+
| Lesson | What the study found |
|
|
358
|
+
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
359
|
+
| "Should" is for people, not a weaker check. | Rules written with "should" and with "must" scored nearly the same ([study](eval/studies/rule-vocabulary.md)). |
|
|
360
|
+
| A wrong finding is usually the rule's fault. | Above 0.8, 31 of 35 wrong findings repeated within a rule: a scope its words reach past, or the same misreading again. Fixing the wording fixes them together. Below 0.8, one-off misreads grow, to about one finding in six at 0.6 to 0.7 ([study](eval/studies/presets.md)). |
|
|
361
|
+
| Narrow a rule in its description. | Jev scores an `excludeIf` near 0.5 for real and false findings alike, so it thins a rule's findings about as much as a higher threshold ([study](eval/studies/presets.md)). Narrowing the description moves the judgment itself: scoping alchemy's idempotent-delete rule to providers' delete handlers dropped its findings elsewhere from 0.67–0.88 to 0.08–0.21, with nothing real lost ([study](eval/studies/idempotent-delete.md)). |
|
|
362
|
+
| Beware rules that turn on what the file does not show. | The weakest preset rules hinged on facts outside the file: whether a client has a default timeout, whether a script is an entry point, whether a key is public ([study](eval/studies/presets.md)). Warning below a context of 0.7 marked one finding in five, and those were real about half the time, where the rest were real three times in four ([study](eval/studies/sufficiency.md)). Make such a rule a warning, or give Jev the fact with config-level [`reads`](#what-jev-reads), an [`appendState`](#rules-as-typescript-files) hook, or an `@adhere` note. |
|
|
363
|
+
| Jev believes comments. | Eight of nine real violations of the idempotent-delete rule carried a comment wrongly saying they were fine, and scored 0.31 to 0.62; without comments, all nine scored 0.81 or more ([study](eval/studies/idempotent-delete.md)). That is why adhere takes comments out. A fact Jev needs, such as an exemption, goes in an `@adhere` note, which stays ([study](eval/studies/comments.md)). |
|
|
350
364
|
|
|
351
365
|
## Commands
|
|
352
366
|
|
|
@@ -358,7 +372,7 @@ writing a rule.
|
|
|
358
372
|
| `adhere init --shared org/repo` | Scaffold a repo of rules other repos install. See [Creating a shared repo](#creating-a-shared-repo). |
|
|
359
373
|
| `adhere list org/repo` | List the rules in another repo's `.adhere/rules/`. See [Install rules from other repos](#install-rules-from-other-repos). |
|
|
360
374
|
| `adhere install org/repo` | Copy them into this repo's `.adhere/rules/org/repo/`. |
|
|
361
|
-
| `adhere login`, `adhere logout` | Save or delete a TypeSafe AI API key. See [Login](#login).
|
|
375
|
+
| `adhere login`, `adhere logout` | Save or delete a TypeSafe AI API key, or with `--openai`, an OpenAI one. See [Login](#login). |
|
|
362
376
|
| `adhere skill [docs\|setup\|fix]` | Print an agent skill. See [Agent skills](#agent-skills). |
|
|
363
377
|
|
|
364
378
|
Bare `adhere` prints the help. `adhere <command> --help` lists a command's
|
|
@@ -400,7 +414,7 @@ Found 1 error.
|
|
|
400
414
|
| `×` or `⚠` | An error, or a warning, in amber, from a rule whose `level` is `warning`. Only errors fail the run. |
|
|
401
415
|
| `data/brand-ports` | The rule's id, then its description. A preset's rule has the preset's name first, as in `effect/basics/gen-for-sequencing`. |
|
|
402
416
|
| `confidence` | Jev's probability that the file breaks the rule. |
|
|
403
|
-
| `context` | Jev's probability that
|
|
417
|
+
| `context` | Jev's probability that it sees enough to decide that. |
|
|
404
418
|
| The excerpt | The section of the file Jev points at. The line it names is underlined, or the lines, when it names several, are marked with a bar beside the code. |
|
|
405
419
|
| `hint:` | The rule's code that must be written. A rule with only code that must never be written shows that code instead, labeled `never:`. |
|
|
406
420
|
| The legend | Under the last finding: each score's threshold, between its range in red and its range in green. |
|
|
@@ -412,10 +426,11 @@ its threshold:
|
|
|
412
426
|
- Red is below the threshold, so lint never reports it: only the legend shows
|
|
413
427
|
that range.
|
|
414
428
|
|
|
415
|
-
**Low context.** Jev sees
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
You may also see a
|
|
429
|
+
**Low context.** Jev sees the file and what adhere [reads](#what-jev-reads)
|
|
430
|
+
beside it, not your whole repo. So we also ask whether that shows enough to
|
|
431
|
+
decide the rule at all. When `context` is below 0.6, it means Jev thinks it
|
|
432
|
+
needs more info, which you can add with an `@adhere` comment. You may also see a
|
|
433
|
+
hint:
|
|
419
434
|
|
|
420
435
|
```text
|
|
421
436
|
warning: this file may not show enough to check this rule
|
|
@@ -487,7 +502,6 @@ const port: number = Number(process.env.PORT);
|
|
|
487
502
|
| `level` | `warning` reports the rule's findings as warnings, which do not fail the run. Use it for a nit, or for a rule that tends to flag code wrongly. |
|
|
488
503
|
| `tests` | Rules skip tests by default. `only` is for a rule about tests, which judges nothing else. `include` is for one that holds in tests as well. |
|
|
489
504
|
| `appliesTo`, `excludeIf` | Where the rule applies. See [Scoping a rule](#scoping-a-rule). |
|
|
490
|
-
| `reads` | What Jev reads beside the code. See [What a rule reads](#what-a-rule-reads). |
|
|
491
505
|
|
|
492
506
|
| Heading in the body | The code under it |
|
|
493
507
|
| ---------------------------- | ----------------------------------------------------------- |
|
|
@@ -554,17 +568,66 @@ finding its matchers dropped, with their scores.
|
|
|
554
568
|
> scored an `excludeIf` near 0.5 for real and false findings alike. See
|
|
555
569
|
> [Writing good rules](#writing-good-rules).
|
|
556
570
|
|
|
557
|
-
### What
|
|
571
|
+
### What Jev reads
|
|
572
|
+
|
|
573
|
+
Beside the file's code, Jev reads three kinds of context, for every rule:
|
|
574
|
+
|
|
575
|
+
| Name | What Jev reads |
|
|
576
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
577
|
+
| `workspacePackages` | The repo's own packages the file imports, each name with its directory, as in `{ "orders-core": "packages/orders" }`. A package it imports that is not listed is an installed one. |
|
|
578
|
+
| `symbols` | The compiler's type blocks for each name, plus compiler documentation and JSDoc tags; not literal editor hover text. |
|
|
579
|
+
| `references` | Whole declarations of symbols the code uses from other files of the repo, keyed by `symbol:repository-relative pathname`: a function with its body, a method without the rest of its class. |
|
|
580
|
+
|
|
581
|
+
`symbols` omits standard-library and Node/Bun runtime built-ins, including their
|
|
582
|
+
methods. Project and third-party symbols remain, even when they share a built-in
|
|
583
|
+
name. Built-in type names can still appear inside those symbols' type signatures.
|
|
584
|
+
|
|
585
|
+
`references` is a flat string-to-string map. Keys keep the symbol's name as used
|
|
586
|
+
in the target code, including import aliases, and the pathname's original casing.
|
|
587
|
+
Values are complete declaration excerpts, not incoming reference call sites.
|
|
588
|
+
Overlapping declarations under one key are merged; disjoint excerpts are joined
|
|
589
|
+
with `…` between them. For example, code importing `twice as scale` can receive:
|
|
590
|
+
|
|
591
|
+
```json
|
|
592
|
+
{
|
|
593
|
+
"references": {
|
|
594
|
+
"__comment": "Declarations of symbols used by the target code, provided only as context. Keys are symbol:repository-relative pathname; values are source excerpts, not incoming call sites.",
|
|
595
|
+
"scale:src/Math.ts": "export function twice(value: number): number {\n return value * 2;\n}"
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
When sent to Jev, each selected `references`, `symbols`, and `workspacePackages`
|
|
601
|
+
map includes a reserved `__comment` explaining its contents. These comments are
|
|
602
|
+
included before `appendState` hooks and restored after hooks. Top-level guidance
|
|
603
|
+
also tells Jev that these maps are context only: judge the target `code`, not the
|
|
604
|
+
additional declarations or metadata.
|
|
605
|
+
|
|
606
|
+
Each symbol's complete resolved content is truncated to 256 characters by
|
|
607
|
+
default. Set `maxSymbolLength` in the config to change this limit. Documentation
|
|
608
|
+
and JSDoc tags are first capped separately by `maxSymbolDocumentationLength`, also
|
|
609
|
+
256 by default; `0` omits documentation and tags. Smaller limits reduce request
|
|
610
|
+
size but can cut off type signatures, overloads, useful details, or examples.
|
|
611
|
+
|
|
612
|
+
To read fewer, list the ones you want in the config's `reads`:
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
// .adhere/config.ts
|
|
616
|
+
import { defineConfig } from "@drkmttr/adhere";
|
|
617
|
+
|
|
618
|
+
export default defineConfig({
|
|
619
|
+
reads: ["workspacePackages"], // no symbols or references
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
`reads: []` sends the code alone.
|
|
558
624
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
code, for that rule only. It is a JSON array on one line in front matter, or an
|
|
562
|
-
array in a config or a TypeScript rule.
|
|
625
|
+
Each name is the key Jev reads it under, so a rule's description can name it in
|
|
626
|
+
backticks, as the questions name `code`:
|
|
563
627
|
|
|
564
628
|
````md
|
|
565
629
|
---
|
|
566
|
-
description: A function from one of this repository's own packages, which `workspacePackages` names, must be called through the package's namespace, never imported by its own name.
|
|
567
|
-
reads: ["workspacePackages"]
|
|
630
|
+
description: A function imported from one of this repository's own packages, which the file's `workspacePackages` names, must be called through the package's namespace, never imported by its own name.
|
|
568
631
|
---
|
|
569
632
|
|
|
570
633
|
## Must
|
|
@@ -580,25 +643,31 @@ import { parse } from "orders-core";
|
|
|
580
643
|
```
|
|
581
644
|
````
|
|
582
645
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
646
|
+
- `symbols` and `references` use the SDK and native compiler pinned to exactly
|
|
647
|
+
`7.1.0-dev.20261009.1`, which the npm package installs. This prerelease contains
|
|
648
|
+
the tuple-reference serialization fix from [microsoft/TypeScript#64080](https://github.com/microsoft/TypeScript/issues/64080);
|
|
649
|
+
SDK and native executable must match that exact version. They use each file's
|
|
650
|
+
`tsconfig.json`, and work without one.
|
|
651
|
+
- `symbols` is still a name-to-string map. Distinct binding/narrowing types get
|
|
652
|
+
distinct blocks; docs and JSDoc tags appear once per canonical symbol per file.
|
|
653
|
+
It has no IDE labels or selected-call overload presentation. Symbol/type queries
|
|
654
|
+
are batched, with independent SDK requests chunked to at most 8.
|
|
591
655
|
- A name adhere does not have refuses the run, so a misspelled one does not go
|
|
592
|
-
unnoticed.
|
|
656
|
+
unnoticed. So does `reads` in a rule: it is the config's alone.
|
|
593
657
|
- What adhere has no name for, such as a file of the repository's, a rule in
|
|
594
658
|
TypeScript adds with [`appendState`](#rules-as-typescript-files).
|
|
595
659
|
|
|
660
|
+
**Cost and privacy.** `symbols` and `references` send Jev more than the file it
|
|
661
|
+
judges: whole declarations from other files, plus compiler-provided documentation
|
|
662
|
+
and JSDoc tags in `symbols`, even with `includeComments` off. The plan's estimate leaves their tokens out. Leave them
|
|
663
|
+
out of `reads` to send less.
|
|
664
|
+
|
|
596
665
|
<details>
|
|
597
|
-
<summary>
|
|
666
|
+
<summary>Where the packages come from</summary>
|
|
598
667
|
|
|
599
|
-
- The packages are read on every run
|
|
600
|
-
|
|
601
|
-
|
|
668
|
+
- The packages are read on every run from the working directory's
|
|
669
|
+
`package.json`, whose `workspaces` npm, Bun, and Yarn read, and from
|
|
670
|
+
`pnpm-workspace.yaml`'s `packages`.
|
|
602
671
|
- In a pattern, `*` is any directory and `**` any depth of them, and one that
|
|
603
672
|
starts with `!` leaves out what it matches. Each matched directory's
|
|
604
673
|
`package.json` gives the name, and its directory is given from the working
|
|
@@ -606,6 +675,8 @@ There is one name so far:
|
|
|
606
675
|
- Nothing need be installed, so a run in CI reads the same packages as one on a
|
|
607
676
|
laptop.
|
|
608
677
|
- A working directory that is not a workspace's root has none.
|
|
678
|
+
- Only an import whose path is a plain string counts, and one of a subpath, such
|
|
679
|
+
as `orders-core/client`, counts as its package's.
|
|
609
680
|
|
|
610
681
|
</details>
|
|
611
682
|
|
|
@@ -637,7 +708,8 @@ export default defineRule({
|
|
|
637
708
|
name `code`.
|
|
638
709
|
- A `RULE.ts` is imported, as a config is, so its code runs on every lint.
|
|
639
710
|
- A rule with a hook goes to Jev in requests of its own, so only that rule reads
|
|
640
|
-
what its hook adds. Each of those requests sends the file
|
|
711
|
+
what its hook adds. Each of those requests sends the file and its context
|
|
712
|
+
again.
|
|
641
713
|
- What the hook reads outside the file is not part of the cache: when
|
|
642
714
|
`db/schema.sql` changes, a file already judged is not judged again until it or
|
|
643
715
|
the rule changes.
|
|
@@ -647,7 +719,8 @@ export default defineRule({
|
|
|
647
719
|
|
|
648
720
|
- The state is what Jev reads beside each question: `code`, the file's sections
|
|
649
721
|
as Jev reads them, under their numbers from 1, as in
|
|
650
|
-
`{ code: { "1": "import …", "2": "export const …" } }
|
|
722
|
+
`{ code: { "1": "import …", "2": "export const …" } }`, beside what the
|
|
723
|
+
config [`reads`](#what-jev-reads).
|
|
651
724
|
- `file` is the file's absolute `path`, and its `contents` as written, comments
|
|
652
725
|
and all.
|
|
653
726
|
- Nothing the hook returns is checked, so it can change `code` too, or replace
|
|
@@ -691,9 +764,9 @@ and they apply project-wide.
|
|
|
691
764
|
## Config
|
|
692
765
|
|
|
693
766
|
A config file is optional. Without one, adhere reads the rules in
|
|
694
|
-
`.adhere/rules/` and any `--preset`. A config names presets, sets the model
|
|
695
|
-
thresholds, or gives rules inline. It sits in the
|
|
696
|
-
being audited, at one of these paths (keep one):
|
|
767
|
+
`.adhere/rules/` and any `--preset`. A config names presets, sets the model,
|
|
768
|
+
the thresholds, and what Jev reads, or gives rules inline. It sits in the
|
|
769
|
+
working directory of the repo being audited, at one of these paths (keep one):
|
|
697
770
|
|
|
698
771
|
- `.adhere/config.ts`, the default, next to the rules
|
|
699
772
|
- `adhere.config.ts`
|
|
@@ -706,7 +779,10 @@ export default defineConfig({
|
|
|
706
779
|
model: "jev-latest",
|
|
707
780
|
threshold: 0.8,
|
|
708
781
|
sufficiencyThreshold: 0.6,
|
|
782
|
+
reads: ["workspacePackages", "symbols", "references"],
|
|
709
783
|
presets: ["effect"],
|
|
784
|
+
maxSymbolDocumentationLength: 256,
|
|
785
|
+
maxSymbolLength: 256,
|
|
710
786
|
exclude: ["**/generated/**"],
|
|
711
787
|
overrides: { "effect/basics/instrument-with-pipe": "off" },
|
|
712
788
|
rules: {
|
|
@@ -724,16 +800,20 @@ type UserId = typeof UserId.Type
|
|
|
724
800
|
});
|
|
725
801
|
```
|
|
726
802
|
|
|
727
|
-
| Key
|
|
728
|
-
|
|
|
729
|
-
| `model`
|
|
730
|
-
| `threshold`
|
|
731
|
-
| `sufficiencyThreshold`
|
|
732
|
-
| `presets`
|
|
733
|
-
| `rules`
|
|
734
|
-
| `exclude`
|
|
735
|
-
| `overrides`
|
|
736
|
-
| `includeComments`
|
|
803
|
+
| Key | Default | Meaning |
|
|
804
|
+
| ------------------------------ | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
805
|
+
| `model` | `"jev-latest"` | The model id sent to TypeSafe. |
|
|
806
|
+
| `threshold` | `0.8` | Report a rule when Jev's probability is above this. |
|
|
807
|
+
| `sufficiencyThreshold` | `0.6` | Below it, a finding warns that Jev may not see enough to decide. |
|
|
808
|
+
| `presets` | none | Built-in rule sets, or their topics. See [Presets](#presets). |
|
|
809
|
+
| `rules` | `.adhere/rules/` | Rules inline, as above, or a directory (see [Where rules live](#where-rules-live)). Either replaces `.adhere/rules/`: none of its rules, root or nested, is read then. |
|
|
810
|
+
| `exclude` | none | Globs, relative to the working directory, of files no rule judges. |
|
|
811
|
+
| `overrides` | none | Settings for a rule by its id. See [Overrides](#overrides). |
|
|
812
|
+
| `includeComments` | `false` | Send every comment to Jev. See [Comments](#comments). |
|
|
813
|
+
| `reads` | `["workspacePackages", "symbols", "references"]` | What Jev reads beside each file's code, for every rule. `[]` is the code alone. See [What Jev reads](#what-jev-reads). |
|
|
814
|
+
| `maxSymbolDocumentationLength` | `256` | Maximum characters of each symbol's combined documentation and JSDoc tags, before `maxSymbolLength` applies. A non-negative integer; `0` omits them. |
|
|
815
|
+
| `maxSymbolLength` | `256` | Maximum characters of each complete `symbols[name]` string, including type signatures and documentation. A non-negative integer; `0` leaves names with empty content. |
|
|
816
|
+
| `provider` | TypeSafe's Jev | Where the questions go instead, with its own model. See [Providers](#providers). |
|
|
737
817
|
|
|
738
818
|
- An invalid shape refuses the run.
|
|
739
819
|
- A config can import other files by relative path, but no packages besides
|
|
@@ -757,6 +837,86 @@ type UserId = typeof UserId.Type
|
|
|
757
837
|
|
|
758
838
|
</details>
|
|
759
839
|
|
|
840
|
+
### Providers
|
|
841
|
+
|
|
842
|
+
adhere asks TypeSafe's Jev by default. A provider asks another decision model
|
|
843
|
+
instead, and adhere exports one for each model
|
|
844
|
+
[the providers study](eval/studies/providers.md) measured:
|
|
845
|
+
|
|
846
|
+
```ts
|
|
847
|
+
import { defineConfig, openai } from "@drkmttr/adhere";
|
|
848
|
+
|
|
849
|
+
// Asks with OPENAI_API_KEY, or the key `adhere login --openai` saved.
|
|
850
|
+
export default defineConfig({ provider: openai() });
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
| Provider | Asks | Options |
|
|
854
|
+
| ------------ | --------------------------------------------------------------- | -------------------------------- |
|
|
855
|
+
| `jev` | TypeSafe's Jev, `jev-latest` by default | `apiKey`, `model` |
|
|
856
|
+
| `openai` | OpenAI's `gpt-6-luna`, on its Decisions API | `apiKey`, `model` |
|
|
857
|
+
| `cloudflare` | Cloudflare's `clef`, by default, or `clef-flash`, on Workers AI | `accountId`, `apiToken`, `model` |
|
|
858
|
+
|
|
859
|
+
- Without an `apiKey`, `jev` and `openai` ask with the key from
|
|
860
|
+
[Login](#login).
|
|
861
|
+
- Each also takes `fetch`, what sends its requests.
|
|
862
|
+
- Clef cuts a request too long for it short rather than refuse it, so
|
|
863
|
+
`cloudflare` skips a file whose request reaches clef's limit: 64,000 tokens,
|
|
864
|
+
or 24,000 for clef-flash.
|
|
865
|
+
|
|
866
|
+
<details>
|
|
867
|
+
<summary>Writing a provider</summary>
|
|
868
|
+
|
|
869
|
+
A provider is a model's name, and an `ask` that answers adhere's questions
|
|
870
|
+
about a state:
|
|
871
|
+
|
|
872
|
+
```ts
|
|
873
|
+
import { defineConfig, type Provider, RequestFailed } from "@drkmttr/adhere";
|
|
874
|
+
|
|
875
|
+
const mine: Provider = {
|
|
876
|
+
model: "my-decision-model",
|
|
877
|
+
ask: async (state, questions, { signal }) => {
|
|
878
|
+
const response = await fetch("https://decisions.example.com/v1/decide", {
|
|
879
|
+
method: "POST",
|
|
880
|
+
headers: { "Content-Type": "application/json" },
|
|
881
|
+
body: JSON.stringify({ state, questions }),
|
|
882
|
+
signal,
|
|
883
|
+
});
|
|
884
|
+
if (!response.ok) {
|
|
885
|
+
throw new RequestFailed(`My model answered HTTP ${response.status}`, response.status);
|
|
886
|
+
}
|
|
887
|
+
return { answers: await response.json() };
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
export default defineConfig({ provider: mine });
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
- `state` is the file's code by section under `code`, beside what adhere read
|
|
895
|
+
for it.
|
|
896
|
+
- Each question has an `id`, the `question` as adhere phrases it, and the
|
|
897
|
+
`fields` it names in backticks, such as `rule`, `must`, and `never`. A `noul`
|
|
898
|
+
asks whether something holds. A `choice` asks for the key of one of its
|
|
899
|
+
`options`, and can have just one.
|
|
900
|
+
- `ask` returns `{ answers }`: by id, a `noul`'s probability from 0 to 1, or a
|
|
901
|
+
`choice`'s key. An id left out goes unanswered: its rule is not reported, and
|
|
902
|
+
is asked again on the next run.
|
|
903
|
+
- A rejection stops the run, but for three errors adhere exports:
|
|
904
|
+
`ContextOverflow` gets one retry without `references` when that map is present;
|
|
905
|
+
if the request still overflows, the file is skipped. `RequestBlocked` reports
|
|
906
|
+
it blocked, and `RequestFailed` is tried again, up to three times, when its
|
|
907
|
+
`status` is 0, for no answer, or a 408, 429, or 5xx.
|
|
908
|
+
- adhere paces calls with `--rpm`, aborts `signal` after 30 seconds, and splits
|
|
909
|
+
requests to fit Jev's 64,000 tokens. A model that takes less splits further in
|
|
910
|
+
its `ask`.
|
|
911
|
+
- `model` replaces the config's `model`. The cache keeps answers by it, so a
|
|
912
|
+
new model is asked again.
|
|
913
|
+
- A provider that sets `savedKey` to `"typesafe"` or `"openai"` gets that key
|
|
914
|
+
as `apiKey`.
|
|
915
|
+
- The executable resolves no packages besides `@drkmttr/adhere`, so an `ask`
|
|
916
|
+
uses `fetch` rather than an SDK.
|
|
917
|
+
|
|
918
|
+
</details>
|
|
919
|
+
|
|
760
920
|
### Presets
|
|
761
921
|
|
|
762
922
|
| Preset | Rules | For | What it asks for |
|
|
@@ -963,9 +1123,9 @@ the default, `0.6`.
|
|
|
963
1123
|
| `--threshold <0 to 1>` | Replace the config's threshold. Per-rule thresholds still apply. |
|
|
964
1124
|
| `--sufficiency-threshold <0 to 1>` | Replace the config's `sufficiencyThreshold`: a finding warns when its `context` is below it. |
|
|
965
1125
|
| `--yes`, `-y` | Send the requests without asking first. |
|
|
966
|
-
| `--limit <
|
|
1126
|
+
| `--limit <files>` | Send requests for at most that many files. See [Limiting a run](#limiting-a-run). |
|
|
967
1127
|
| `--rpm <requests>` | Send at most that many requests a minute. |
|
|
968
|
-
| `--filter <glob>` |
|
|
1128
|
+
| `--filter <glob>` | Judge only the files a glob matches. |
|
|
969
1129
|
| `--deny-warnings` | Fail on warnings as well as errors. |
|
|
970
1130
|
| `--log-level <level>` | Log the run's work on stderr. See [Logging a run](#logging-a-run). |
|
|
971
1131
|
|
|
@@ -990,9 +1150,7 @@ the default, `0.6`.
|
|
|
990
1150
|
```
|
|
991
1151
|
|
|
992
1152
|
**Tests** are judged only by rules about tests, whose front matter says `tests`
|
|
993
|
-
(see [Rules as Markdown files](#rules-as-markdown-files)).
|
|
994
|
-
the findings from rules not about tests were in test helpers and fixtures. A
|
|
995
|
-
test is:
|
|
1153
|
+
(see [Rules as Markdown files](#rules-as-markdown-files)). A test is:
|
|
996
1154
|
|
|
997
1155
|
- a `.test` or `.spec` file, such as `a.test.ts` or `Button.spec.tsx`;
|
|
998
1156
|
- a fixture or test helper named as one, such as `fixtures.ts`,
|
|
@@ -1008,17 +1166,19 @@ takes, and asks:
|
|
|
1008
1166
|
|
|
1009
1167
|
```text
|
|
1010
1168
|
200 files and 14 rules: 2800 checks, 1400 cached.
|
|
1011
|
-
Judging
|
|
1169
|
+
Judging 100 files takes an estimated 100 requests to Jev, plus 1 or more for each file with a finding.
|
|
1012
1170
|
Those carry about 1.9 million input tokens: about $0.08 at $0.042 per million, and more for locating findings.
|
|
1013
|
-
|
|
1171
|
+
Estimates cover only code and workspace-package context; symbol/reference input, possible extra request splits, and size-skips are determined at send time.
|
|
1172
|
+
? Estimates cover only code and workspace-package context; symbol/reference input, possible extra request splits, and size-skips are determined at send time. Send an estimated 100 requests to Jev, about $0.08? › (Y/n)
|
|
1014
1173
|
```
|
|
1015
1174
|
|
|
1016
|
-
- It asks only with a terminal on stdin and stdout, and
|
|
1017
|
-
|
|
1175
|
+
- It asks only with a terminal on stdin and stdout, and only when there is
|
|
1176
|
+
something to send. `--yes` sends without asking.
|
|
1018
1177
|
- The cost is adhere's estimate of the input tokens times the model's price. Jev
|
|
1019
1178
|
charges only for input tokens: $0.042 a million for `jev-latest`, per
|
|
1020
1179
|
[TypeSafe AI's models page](https://docs.typesafe.ai/models) in
|
|
1021
|
-
September 2026.
|
|
1180
|
+
September 2026. The estimate leaves out `symbols` and `references`, which
|
|
1181
|
+
adhere reads as it sends each file, so a run can cost more than it says.
|
|
1022
1182
|
- A run that stops loses nothing: files finished before it stopped are cached,
|
|
1023
1183
|
so running again continues from there.
|
|
1024
1184
|
|
|
@@ -1040,22 +1200,24 @@ Those carry about 1.9 million input tokens: about $0.08 at $0.042 per million, a
|
|
|
1040
1200
|
|
|
1041
1201
|
Three flags bound what a run does:
|
|
1042
1202
|
|
|
1043
|
-
| Flag | Effect
|
|
1044
|
-
| ------------------ |
|
|
1045
|
-
| `--limit <
|
|
1046
|
-
| `--rpm <requests>` | Sends at most that many requests to Jev a minute, evenly spaced, retries included. The plan says about how long they take.
|
|
1047
|
-
| `--filter <glob>` |
|
|
1203
|
+
| Flag | Effect |
|
|
1204
|
+
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1205
|
+
| `--limit <files>` | Sends requests for at most that many files, taken in path order. The rest wait, and since judgments are cached, the next run with the same limit picks up where this one stopped. `--limit 0` shows the plan and sends nothing. |
|
|
1206
|
+
| `--rpm <requests>` | Sends at most that many requests to Jev a minute, evenly spaced, retries included. The plan says about how long they take. |
|
|
1207
|
+
| `--filter <glob>` | Judges only the files whose path from the working directory matches, such as `src/**` or `**/*.service.ts`. Wrap a glob in single quotes so the shell does not expand it. Repeat the flag for more, and start a pattern with `!` to leave out what it matches. |
|
|
1048
1208
|
|
|
1049
1209
|
```sh
|
|
1050
|
-
adhere lint --filter 'packages/api/**' --filter '!**/generated/**' --limit
|
|
1210
|
+
adhere lint --filter 'packages/api/**' --filter '!**/generated/**' --limit 100 --rpm 30
|
|
1051
1211
|
```
|
|
1052
1212
|
|
|
1213
|
+
`--limit` counts files, not requests: one file can take several.
|
|
1214
|
+
|
|
1053
1215
|
### Logging a run
|
|
1054
1216
|
|
|
1055
|
-
| Level | What it logs, on stderr
|
|
1056
|
-
| ------------------- |
|
|
1057
|
-
| `--log-level debug` | The config it loaded, how many paths it listed and how many of them it reads, and each request to Jev, with the file it is for, about how many tokens it carries,
|
|
1058
|
-
| `--log-level trace` | Also each file as it is read, planned, and answered from the cache
|
|
1217
|
+
| Level | What it logs, on stderr |
|
|
1218
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1219
|
+
| `--log-level debug` | The config it loaded, how many paths it listed and how many of them it reads, and each request to Jev, with the file it is for, about how many tokens it carries, and how long it took. A failed attempt is logged even when a retry hides it, with the HTTP status, Cloudflare's Ray ID, and the start of what the API said. |
|
|
1220
|
+
| `--log-level trace` | Also each file as it is read, planned, and answered from the cache. |
|
|
1059
1221
|
|
|
1060
1222
|
stdout still carries only the report, so the log can go to a file of its own:
|
|
1061
1223
|
|
|
@@ -1076,8 +1238,11 @@ adhere lint --log-level debug 2> adhere.log
|
|
|
1076
1238
|
### Comments
|
|
1077
1239
|
|
|
1078
1240
|
Jev reads a file's code, not its comments. adhere takes every comment out before
|
|
1079
|
-
it hashes or sends a file,
|
|
1080
|
-
|
|
1241
|
+
it hashes or sends a file, or a declaration from
|
|
1242
|
+
[`references`](#what-jev-reads), blanking it so every line keeps its number.
|
|
1243
|
+
The report's excerpts still show them. Compiler-provided documentation and
|
|
1244
|
+
JSDoc tags still reach Jev in the type/docs strings [`symbols`](#what-jev-reads)
|
|
1245
|
+
sends; these are not editor hovers.
|
|
1081
1246
|
|
|
1082
1247
|
Jev takes what comments claim at their word, which hid real violations in the
|
|
1083
1248
|
studies: see [Writing good rules](#writing-good-rules).
|
|
@@ -1128,15 +1293,15 @@ paid request, and Jev's answers vary a little from run to run, so a committed
|
|
|
1128
1293
|
cache gives everyone and CI the same findings without paying for them again, and
|
|
1129
1294
|
a pull request changes the judgments of only the files it changes.
|
|
1130
1295
|
|
|
1131
|
-
| What changed
|
|
1132
|
-
|
|
|
1133
|
-
| A file's code
|
|
1134
|
-
| A file's comments alone
|
|
1135
|
-
| A file is moved or copied
|
|
1136
|
-
| A rule's text, a matcher, or the source of its `appendState`
|
|
1137
|
-
|
|
|
1138
|
-
| The threshold is lowered
|
|
1139
|
-
| adhere asks its questions differently, after an upgrade
|
|
1296
|
+
| What changed | What is judged again |
|
|
1297
|
+
| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
1298
|
+
| A file's code | Every rule, for that file. |
|
|
1299
|
+
| A file's comments alone | Nothing. |
|
|
1300
|
+
| A file is moved or copied | Nothing. Identical files share their judgments. |
|
|
1301
|
+
| A rule's text, a matcher, or the source of its `appendState` | That rule, for every file. |
|
|
1302
|
+
| Anything else Jev read: a file it imports, the workspace's packages, or the config's `reads` | Nothing, so a judgment can go stale. Delete the cache to judge every file again. |
|
|
1303
|
+
| The threshold is lowered | Nothing. Cached judgments newly above it are located. |
|
|
1304
|
+
| adhere asks its questions differently, after an upgrade | Every rule, once. |
|
|
1140
1305
|
|
|
1141
1306
|
To keep the cache out of diffs, mark it as generated in `.gitattributes`:
|
|
1142
1307
|
|
|
@@ -1159,8 +1324,8 @@ To keep the cache out of diffs, mark it as generated in `.gitattributes`:
|
|
|
1159
1324
|
- `files/` holds Jev's answers by the content they are about. Each cache file is
|
|
1160
1325
|
named for the hash of a source file's content as Jev reads it, without its
|
|
1161
1326
|
comments.
|
|
1162
|
-
- Each answer sits under a fingerprint of the model and
|
|
1163
|
-
|
|
1327
|
+
- Each answer sits under a fingerprint of the model and the rule: its text, its
|
|
1328
|
+
matchers, and its `appendState`'s source.
|
|
1164
1329
|
- `tallies/` keeps the linter check's answers, one tally per rule, under a key
|
|
1165
1330
|
that changes with the rule's text and the model.
|
|
1166
1331
|
- A cache file is written once and never changed: it is also named for the hash
|
|
@@ -1235,7 +1400,7 @@ adhere skill fix | codex exec --sandbox workspace-write \
|
|
|
1235
1400
|
2. **Judge.** One request per file asks Jev, for each rule, how likely the file
|
|
1236
1401
|
is to break it.
|
|
1237
1402
|
3. **Locate.** A second request, only for rules above their threshold, asks
|
|
1238
|
-
which section and lines break the rule, and whether
|
|
1403
|
+
which section and lines break the rule, and whether Jev sees enough to
|
|
1239
1404
|
decide.
|
|
1240
1405
|
|
|
1241
1406
|
A file too long for Jev's context is skipped and counted in the summary.
|
|
@@ -1250,9 +1415,9 @@ statements of its body. Jev reads the file as these sections.
|
|
|
1250
1415
|
|
|
1251
1416
|
**Judge: one request per file.**
|
|
1252
1417
|
|
|
1253
|
-
- The state is the file's sections under their numbers from 1, as
|
|
1254
|
-
`{ "1": "…", "2": "…" }`, without their comments (see
|
|
1255
|
-
|
|
1418
|
+
- The state is `code`, the file's sections under their numbers from 1, as
|
|
1419
|
+
`{ code: { "1": "…", "2": "…" } }`, without their comments (see
|
|
1420
|
+
[Comments](#comments)), beside what the config [`reads`](#what-jev-reads).
|
|
1256
1421
|
- Each rule is one `noul` (yes/no probability) question that carries the rule's
|
|
1257
1422
|
description and details, and its code under its words, `must` and `never`, or a guideline's
|
|
1258
1423
|
`should` and `should_not`. It asks whether the file diverges from the pattern
|
|
@@ -1260,14 +1425,14 @@ statements of its body. Jev reads the file as these sections.
|
|
|
1260
1425
|
only code that must never be written, whether the file contains that code.
|
|
1261
1426
|
- Criteria for each answer draw the line at the rule's scope, so a file with no
|
|
1262
1427
|
code the rule is about is a no.
|
|
1263
|
-
- No rule sits in the
|
|
1264
|
-
|
|
1428
|
+
- No rule sits in the state, so a rule's probability depends only on the state
|
|
1429
|
+
and that rule, not on which other rules share the request. Every question
|
|
1265
1430
|
shares the state cost of the request.
|
|
1266
1431
|
- On up to 10 files per project rule, the rule's question has a second one
|
|
1267
1432
|
beside it, the linter check (see
|
|
1268
1433
|
[Check for contradictions](#check-for-contradictions)), with the same fields.
|
|
1269
|
-
- A rule with `appendState`
|
|
1270
|
-
|
|
1434
|
+
- A rule with `appendState` has requests of its own, here and below, whose
|
|
1435
|
+
state adds to this one.
|
|
1271
1436
|
|
|
1272
1437
|
**Locate: a second request, only when at least one rule's probability is above
|
|
1273
1438
|
its threshold.** Per flagged rule:
|
|
@@ -1283,18 +1448,23 @@ its threshold.** Per flagged rule:
|
|
|
1283
1448
|
for 94% of real findings, most often as one line
|
|
1284
1449
|
([study](eval/studies/pinpoint.md)).
|
|
1285
1450
|
- One `noul` carrying the rule: whether you can tell if `code` breaks it from
|
|
1286
|
-
|
|
1287
|
-
configuration do. It yields the `context` score and its
|
|
1451
|
+
the state alone, without knowing what other files, libraries, services, or
|
|
1452
|
+
configuration do beyond what it shows. It yields the `context` score and its
|
|
1453
|
+
warning.
|
|
1288
1454
|
|
|
1289
1455
|
**Limits.** Jev reads at most 32k tokens of state and one question together, and
|
|
1290
1456
|
64k tokens in a request. adhere estimates tokens from the JSON it sends, at
|
|
1291
|
-
about three bytes a token.
|
|
1457
|
+
about three bytes a token, and leaves 1k of room for what Jev wraps around it.
|
|
1292
1458
|
|
|
1293
1459
|
- When a file's questions would not fit in one request, they are split across
|
|
1294
1460
|
several.
|
|
1295
|
-
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1461
|
+
- If a file's state and longest question would not fit together, or a provider
|
|
1462
|
+
reports a context overflow, adhere retries once without `references` when that
|
|
1463
|
+
map is present. Target code, `symbols`, and other context remain intact; no
|
|
1464
|
+
declaration excerpt is shortened to fit. Removing references may still leave
|
|
1465
|
+
the request too large, in which case the file is skipped and counted in the
|
|
1466
|
+
summary. A file with more than 255 code sections is also skipped, and the rest
|
|
1467
|
+
of the run goes on.
|
|
1298
1468
|
- So is a file Jev itself counts as over its context, which text denser than the
|
|
1299
1469
|
estimate, such as CJK, can cause.
|
|
1300
1470
|
|
|
@@ -1310,8 +1480,10 @@ Names and layouts from older versions:
|
|
|
1310
1480
|
| through 0.7 | The `effect` preset checked seven conventions a linter checks exactly. | They are left to `@effect/tsgo`. The notes on `effect` under [Presets](#presets) say how to turn them on. |
|
|
1311
1481
|
| before 0.13 | A rule was any `*.md` file in `.adhere/`, as `.adhere/data/brand-ports.md`. | A `*.md` file outside every rule's directory, but a `README.md`, refuses the run. The refusal says where each such file goes to keep its id, so `adhere-ignore` comments and `overrides` still name it. |
|
|
1312
1482
|
| before 0.13 | `.adhere/tsconfig.json` included only `config.ts`. | Make its `include` `["**/*.ts"]` to give rules written in TypeScript their types. |
|
|
1313
|
-
| 0.13.1 | The workspace's packages had a key of their own, `includeWorkspacePackages: true`. | adhere no longer reads it
|
|
1483
|
+
| 0.13.1 | The workspace's packages had a key of their own, `includeWorkspacePackages: true`. | adhere no longer reads it. Jev reads the workspace's packages by default. |
|
|
1314
1484
|
| 0.13.1 and before | Prose around a `RULE.md`'s code was ignored. | Jev reads it after the description, as the rule's details, so a rule with prose is judged again once. Keep prose about the rule: why it holds, or where it does not apply. |
|
|
1485
|
+
| 0.15.0 and before | A rule named what Jev reads beside its code, in `reads`. | `reads` is the config's, for every rule, and a rule that has it refuses the run: delete it. Jev reads all three by default. See [What Jev reads](#what-jev-reads). |
|
|
1486
|
+
| 0.15.0 and before | `--limit` counted rule checks. | It counts files. |
|
|
1315
1487
|
| 0.14.0 and before | The `effect` preset had `services/test-layers-are-in-memory` and `services/operations-have-no-requirements`. | They are gone, and an `overrides` entry naming either refuses the run: remove it. `@effect/tsgo`'s `leakingRequirements` checks the second. `adhere-ignore` comments naming them suppress nothing. |
|
|
1316
1488
|
|
|
1317
1489
|
## Development
|
|
@@ -1325,24 +1497,31 @@ adhere lint --preset effect
|
|
|
1325
1497
|
```
|
|
1326
1498
|
|
|
1327
1499
|
- In a checkout no platform package is installed, so `bin/adhere.js` runs
|
|
1328
|
-
`src/main.ts` with Bun instead
|
|
1500
|
+
`src/main.ts` with Bun instead, and `symbols` and `references` use the
|
|
1501
|
+
checkout's own TypeScript.
|
|
1329
1502
|
- `bun run typecheck` runs `tsc` and the linter.
|
|
1330
1503
|
- `bun run test` runs the tests.
|
|
1504
|
+
- `bun scripts/verify-npm.ts`, after `bun run build:npm`, installs the built
|
|
1505
|
+
packages in a scratch project and checks that they read `symbols` and
|
|
1506
|
+
`references`. It needs Node 24 or later, and sends Jev nothing.
|
|
1331
1507
|
|
|
1332
1508
|
### Native executable
|
|
1333
1509
|
|
|
1334
|
-
| Command | What it builds
|
|
1335
|
-
| ------------------- |
|
|
1336
|
-
| `bun run build` | `dist/adhere`, a single binary with Bun and the presets inside it, through Bun's [`--compile`](https://bun.sh/docs/bundler/executables) with `--asset ./presets`. It runs without Bun or `node_modules` on the target machine and still loads the repo's `config.ts` and Markdown rules from disk. |
|
|
1337
|
-
| `bun run build:npm` | The same for every published platform, each into its package under `dist/npm/`, with a copy for the GitHub release in `dist/release/` ([`scripts/npm-packages.ts`](./scripts/npm-packages.ts)).
|
|
1510
|
+
| Command | What it builds |
|
|
1511
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1512
|
+
| `bun run build` | `dist/adhere`, a single binary with Bun and the presets inside it, through Bun's [`--compile`](https://bun.sh/docs/bundler/executables) with `--asset ./presets`. It runs without Bun or `node_modules` on the target machine and still loads the repo's `config.ts` and Markdown rules from disk. It has no TypeScript compiler, so it reads no `symbols` or `references`. |
|
|
1513
|
+
| `bun run build:npm` | The same for every published platform, each into its package under `dist/npm/`, with a copy for the GitHub release in `dist/release/` ([`scripts/npm-packages.ts`](./scripts/npm-packages.ts)). |
|
|
1338
1514
|
|
|
1339
1515
|
Each platform's executable is its own npm package,
|
|
1340
1516
|
`@drkmttr/adhere-<platform>-<arch>`, limited by `os` and `cpu`, and an optional
|
|
1341
1517
|
dependency of `@drkmttr/adhere`, so an install fetches only its own machine's.
|
|
1342
|
-
|
|
1518
|
+
Each depends on TypeScript's native compiler for its platform,
|
|
1519
|
+
`@typescript/typescript-<platform>-<arch>`, pinned to exactly
|
|
1520
|
+
`7.1.0-dev.20261009.1` to match the SDK. The package's
|
|
1521
|
+
`bin/adhere.js` finds both, and runs the executable with Node. The same
|
|
1343
1522
|
executables are attached to each GitHub release, named for `uname -s` and
|
|
1344
|
-
`uname -m` as in `adhere-Linux-x86_64`,
|
|
1345
|
-
|
|
1523
|
+
`uname -m` as in `adhere-Linux-x86_64`, with `LICENSE` and
|
|
1524
|
+
`THIRD_PARTY_NOTICES.txt`, which go with any copy you redistribute.
|
|
1346
1525
|
|
|
1347
1526
|
`adhere --version` tells the builds apart:
|
|
1348
1527
|
|
|
@@ -1376,6 +1555,9 @@ git push origin v0.3.0
|
|
|
1376
1555
|
Release, and publishes each package before `@drkmttr/adhere`, which it first
|
|
1377
1556
|
lists them in as `optionalDependencies` at the same version.
|
|
1378
1557
|
|
|
1558
|
+
CI, and the publish workflow before it publishes, run `scripts/verify-npm.ts`
|
|
1559
|
+
on Linux and on both Macs. Windows is built, but not run.
|
|
1560
|
+
|
|
1379
1561
|
<details>
|
|
1380
1562
|
<summary>More on publishing</summary>
|
|
1381
1563
|
|