@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.
- package/README.md +294 -146
- package/bin/adhere.js +19 -4
- package/package.json +34 -12
- package/presets/effect/config/tests-provide-values-directly/RULE.md +12 -1
- package/presets/effect/data/brand-meaningful-primitives/RULE.md +14 -2
- package/presets/effect/data/variants-are-tagged-unions/RULE.md +17 -4
- package/presets/effect/services/dependencies-through-layers/RULE.md +16 -3
- package/presets/effect/services/no-mutable-state/RULE.md +7 -1
- 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 +49 -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 +165 -170
- package/src/services/Jev.provider.test.ts +207 -0
- package/src/services/Jev.reads.test.ts +439 -0
- package/src/services/Jev.ts +205 -113
- package/src/services/NativeLsp.ts +289 -0
- package/src/services/SourceReads.test.ts +1040 -0
- package/src/services/SourceReads.ts +437 -0
- package/src/services/SourceWalker.ts +26 -59
- package/src/workflows/audit.ts +440 -265
- package/src/workflows/format.ts +74 -17
- package/src/workspaces.test.ts +73 -0
- package/src/workspaces.ts +91 -0
- package/presets/effect/services/operations-have-no-requirements/RULE.md +0 -50
- 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).
|
|
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
|
|
28
|
-
|
|
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.
|
|
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.
|
|
48
|
-
"vite
|
|
49
|
-
"
|
|
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.
|
|
72
|
-
"@drkmttr/adhere-darwin-x64": "0.
|
|
73
|
-
"@drkmttr/adhere-linux-arm64": "0.
|
|
74
|
-
"@drkmttr/adhere-linux-x64": "0.
|
|
75
|
-
"@drkmttr/adhere-win32-x64": "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
|
|
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
|
|
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
|
-
|
|
40
|
+
email: Schema.String,
|
|
29
41
|
});
|
|
30
42
|
```
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
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
|
|
42
|
+
type Payment = { kind: "card"; last4: string } | { kind: "invoice"; dueDate: string };
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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 = (
|
|
35
|
-
|
|
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:
|
|
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
|
package/skills/adhere/SKILL.md
CHANGED
|
@@ -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 #
|
|
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
|
|
125
|
-
|
|
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,
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
193
|
-
claiming code is safe hid real violations in the evals.
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
214
|
-
|
|
215
|
-
and a committed cache gives the team and CI the same findings without
|
|
216
|
-
again.
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
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
|
|
245
|
-
enough to decide
|
|
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
|