lanekeep 0.4.0 → 0.6.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 +287 -133
- package/builtin.d.ts +24 -0
- package/index.d.ts +308 -0
- package/index.js +15 -0
- package/package.json +17 -5
package/README.md
CHANGED
|
@@ -2,220 +2,355 @@
|
|
|
2
2
|
|
|
3
3
|
**Deterministic, AST-based architectural conformance checking for AI-generated and human-written code.**
|
|
4
4
|
|
|
5
|
+
[](https://crates.io/crates/lanekeep-cli)
|
|
6
|
+
[](https://www.npmjs.com/package/lanekeep)
|
|
7
|
+
[](https://pypi.org/project/lanekeep/)
|
|
8
|
+
[](https://github.com/fmsouza/lanekeep/actions/workflows/ci.yml)
|
|
5
9
|
[](#license)
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
lanekeep enforces the conventions that live in your team's heads and your reviewers' comments —
|
|
12
|
+
the ones a language model cannot infer from the code it is shown. Every rule is a codified answer
|
|
13
|
+
to **"the agent keeps doing this wrong."**
|
|
14
|
+
|
|
15
|
+
Checks **TypeScript, JavaScript, Python, Go and Rust**. Ships as a single static binary with no runtime
|
|
16
|
+
dependency.
|
|
10
17
|
|
|
11
18
|
---
|
|
12
19
|
|
|
13
|
-
##
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
Sixty seconds, from nothing to a rule catching something.
|
|
23
|
+
|
|
24
|
+
**1. Install** — whichever fits the project you are adding it to:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --save-dev lanekeep
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
<details>
|
|
31
|
+
<summary>Python, Go, Homebrew, cargo, or a raw binary</summary>
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install lanekeep # Python
|
|
35
|
+
go get -tool github.com/fmsouza/lanekeep/cmd/lanekeep # Go
|
|
36
|
+
brew install fmsouza/tap/lanekeep # macOS / Linux, system-wide
|
|
37
|
+
cargo install lanekeep-cli # from source
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or download from the [releases page](https://github.com/fmsouza/lanekeep/releases).
|
|
41
|
+
|
|
42
|
+
</details>
|
|
43
|
+
|
|
44
|
+
**2. Scaffold a config and a first rule:**
|
|
14
45
|
|
|
15
|
-
|
|
16
|
-
lanekeep
|
|
17
|
-
|
|
46
|
+
```bash
|
|
47
|
+
npx lanekeep init
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That writes two files, both runnable:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
lanekeep.json # what to check, and with which rules
|
|
54
|
+
lanekeep/rules/<starter>.ts # a worked example you can edit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It detects whether the project is Go, Python or TypeScript and scaffolds accordingly — the
|
|
58
|
+
right glob, a starter rule in that language, and a built-in worth having on.
|
|
18
59
|
|
|
19
|
-
|
|
60
|
+
**3. Check:**
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx lanekeep check
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/payment.ts:12:3 error [local/no-debugger] debugger statement
|
|
68
|
+
→ remove it before committing
|
|
69
|
+
|
|
70
|
+
✖ 1 error(s) across 1 file(s) checked
|
|
71
|
+
```
|
|
20
72
|
|
|
21
|
-
|
|
73
|
+
> **If it says `0 file(s) checked`**, nothing matched the config's `include`. The scaffold starts
|
|
74
|
+
> with `src/**/*.{ts,tsx}` — widen it to wherever your code actually lives.
|
|
75
|
+
|
|
76
|
+
That is the whole loop. Everything below is detail.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## What it is
|
|
81
|
+
|
|
82
|
+
lanekeep is not a linter in the ESLint sense. ESLint enforces language-level correctness; lanekeep
|
|
83
|
+
enforces *project-specific* conventions. The two do not overlap much, and lanekeep is not a
|
|
84
|
+
replacement for either your linter or your formatter.
|
|
85
|
+
|
|
86
|
+
Rules are TypeScript programs. Here is one checking **Go**:
|
|
22
87
|
|
|
23
88
|
```ts
|
|
24
89
|
import { defineRule } from 'lanekeep'
|
|
25
90
|
|
|
26
91
|
export default defineRule({
|
|
27
|
-
id: 'local/no-
|
|
92
|
+
id: 'local/no-fmt-println',
|
|
93
|
+
language: 'go',
|
|
28
94
|
severity: 'error',
|
|
29
95
|
|
|
30
96
|
card: {
|
|
31
|
-
message: '
|
|
32
|
-
remediation: '
|
|
33
|
-
examples: {
|
|
97
|
+
message: 'fmt.Println in library code',
|
|
98
|
+
remediation: 'use log/slog, so the output has a level and a destination',
|
|
99
|
+
examples: {
|
|
100
|
+
bad: 'fmt.Println("saved", count)',
|
|
101
|
+
good: 'slog.Info("saved", "count", count)',
|
|
102
|
+
},
|
|
34
103
|
},
|
|
35
104
|
|
|
36
105
|
// Matched in Rust, at native speed. Your code runs only on matches.
|
|
37
106
|
query: `
|
|
38
|
-
(
|
|
39
|
-
|
|
40
|
-
|
|
107
|
+
(call_expression
|
|
108
|
+
function: (selector_expression
|
|
109
|
+
operand: (identifier) @pkg
|
|
110
|
+
field: (field_identifier) @fn)) @call
|
|
41
111
|
`,
|
|
42
112
|
|
|
43
113
|
check(ctx, m) {
|
|
44
|
-
if (
|
|
45
|
-
if (
|
|
114
|
+
if (ctx.text(m.pkg) !== 'fmt') return
|
|
115
|
+
if (ctx.text(m.fn) !== 'Println') return
|
|
46
116
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
117
|
+
// The line that makes this a rule rather than a grep: a local variable
|
|
118
|
+
// named `fmt` is not the standard library package.
|
|
119
|
+
if (ctx.bindingKind(m.pkg) !== 'import') return
|
|
50
120
|
|
|
51
|
-
ctx.report(m.
|
|
121
|
+
ctx.report(m.call)
|
|
52
122
|
},
|
|
53
123
|
})
|
|
54
124
|
```
|
|
55
125
|
|
|
126
|
+
**Rules are TypeScript whatever they check** — that is one embedded language, not a JavaScript
|
|
127
|
+
bias. Rules need to be *programs*, because the conventions worth enforcing are too specific for
|
|
128
|
+
any fixed vocabulary of predicates, and one language keeps the sandbox, the cache and the host
|
|
129
|
+
API single-implementation.
|
|
130
|
+
|
|
131
|
+
**Configuration is not TypeScript.** `lanekeep.json` is plain data, so a Go or Python team never
|
|
132
|
+
writes a `.ts` file except when authoring an actual rule.
|
|
133
|
+
|
|
56
134
|
`check` is ordinary TypeScript. Loop, accumulate state, build data structures, read other files,
|
|
57
135
|
import shared helpers — there is no expressiveness ceiling and no DSL to learn beyond the query
|
|
58
136
|
that gates it.
|
|
59
137
|
|
|
60
|
-
|
|
138
|
+
**The card is not documentation.** `message`, `remediation` and `examples` are mandatory, because
|
|
139
|
+
they are what gets fed back to whoever has to act on the violation — increasingly an agent.
|
|
61
140
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
141
|
+
**Editor types ship with the npm package.** `npm install --save-dev lanekeep` gives you the
|
|
142
|
+
binary *and* TypeScript definitions for the whole host API, so `ctx` autocompletes and a typo'd
|
|
143
|
+
method is a compile error rather than a rule that throws in the sandbox. They are checked
|
|
144
|
+
against the engine's own registration, so they cannot drift from what actually exists.
|
|
65
145
|
|
|
66
|
-
|
|
146
|
+
A Go, Python or Rust project that wants them can add the npm package as a dev dependency
|
|
147
|
+
purely for authoring — nothing about the checker needs Node.
|
|
67
148
|
|
|
68
|
-
|
|
69
|
-
on a couple of thousand files has a sub-second budget and a warm run has a sub-25ms one.
|
|
70
|
-
- **Its output is read by a machine.** Violations are sorted deterministically, because an agent
|
|
71
|
-
that reads the output twice must not see reordering as change.
|
|
72
|
-
- **Every rule carries its own fix.** `message`, `remediation` and `examples` are mandatory
|
|
73
|
-
fields, not documentation — they are the rule card that gets fed back to the agent.
|
|
149
|
+
## Supported languages
|
|
74
150
|
|
|
75
|
-
|
|
151
|
+
Each guide covers installing lanekeep in that ecosystem, what to put in the config, which
|
|
152
|
+
built-in rules apply, a worked custom rule, and the resolution behavior specific to it.
|
|
76
153
|
|
|
77
|
-
|
|
78
|
-
|
|
154
|
+
| Language | Guide | Extensions |
|
|
155
|
+
| --- | --- | --- |
|
|
156
|
+
| Go | **[Go guide](https://github.com/fmsouza/lanekeep/wiki/Go)** | `.go` |
|
|
157
|
+
| Python | **[Python guide](https://github.com/fmsouza/lanekeep/wiki/Python)** | `.py`, `.pyi` |
|
|
158
|
+
| Rust | **[Rust guide](https://github.com/fmsouza/lanekeep/wiki/Rust)** | `.rs` |
|
|
159
|
+
| TypeScript / JavaScript | **[TypeScript and JavaScript guide](https://github.com/fmsouza/lanekeep/wiki/TypeScript-and-JavaScript)** | `.ts`, `.mts`, `.cts`, `.tsx`, `.js`, `.mjs`, `.cjs`, `.jsx` |
|
|
79
160
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
TypeScript.
|
|
161
|
+
Every one carries syntactic binding resolution, so a rule can ask where a name came from rather
|
|
162
|
+
than matching text — `ctx.bindingKind`, `ctx.resolvesToImport` and `ctx.isShadowed` answer for
|
|
163
|
+
all of them.
|
|
84
164
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
└─miss─> path and raw-text gates reject before any parse
|
|
90
|
-
└─> parse ─> match queries in Rust
|
|
91
|
-
└─> invoke the TypeScript handler, per match only
|
|
92
|
-
└─> reduce phase: cross-file rules consume facts only, never parse trees
|
|
93
|
-
└─> filter suppressions ─> sort ─> report
|
|
94
|
-
```
|
|
165
|
+
**The grammar is chosen by the file, not by the rule.** A rule declares which languages it
|
|
166
|
+
applies to and does not run on files of any other, defaulting to `['typescript', 'tsx']` when it
|
|
167
|
+
says nothing. That default is the one thing to get right on a non-TypeScript rule: omit
|
|
168
|
+
`language` on a Go rule and it silently never fires.
|
|
95
169
|
|
|
96
|
-
|
|
170
|
+
## Configuration
|
|
97
171
|
|
|
98
|
-
|
|
172
|
+
`lanekeep.json`, at the project root. `lanekeep init` writes one for you, matched to the
|
|
173
|
+
project it finds.
|
|
99
174
|
|
|
100
|
-
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"$schema": "https://raw.githubusercontent.com/fmsouza/lanekeep/main/schema/lanekeep.schema.json",
|
|
101
178
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
pip install lanekeep # Python
|
|
105
|
-
brew install fmsouza/tap/lanekeep # macOS and Linux, system-wide
|
|
106
|
-
cargo install lanekeep-cli # from source
|
|
107
|
-
```
|
|
179
|
+
"include": ["**/*.go"],
|
|
180
|
+
"exclude": ["**/*_test.go"],
|
|
108
181
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
go
|
|
182
|
+
"rules": [
|
|
183
|
+
"lanekeep/no-package-init",
|
|
184
|
+
{ "rule": "lanekeep/no-restricted-imports", "options": { "restrictions": [
|
|
185
|
+
{ "module": "database/sql", "from": ["!internal/store/**"], "reason": "go through the store package" }
|
|
186
|
+
] } },
|
|
187
|
+
"./lanekeep/rules/no-fmt-println.ts"
|
|
188
|
+
]
|
|
189
|
+
}
|
|
113
190
|
```
|
|
114
191
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
already-installed lanekeep and it fetches nothing.
|
|
192
|
+
A string uses a rule as it comes; the object form calls it with options. `$schema` is what
|
|
193
|
+
gives you **completion and validation in your editor with nothing installed** — VS Code and
|
|
194
|
+
most others read it directly.
|
|
119
195
|
|
|
120
|
-
|
|
196
|
+
**Rules are TypeScript, configuration is not.** A rule is a program, and that is the point of
|
|
197
|
+
the tool; saying which rules to run is data. A Go or Python team should not have to write a
|
|
198
|
+
`.ts` file to do the second, which is why the config is JSON and only the rules are not.
|
|
121
199
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
dependency any of those ways.
|
|
200
|
+
Rule ids are namespaced. `lanekeep/` is reserved for built-ins and `local/` needs no
|
|
201
|
+
declaration; any other prefix must be listed in `namespaces`, so a typo in an id is an error
|
|
202
|
+
rather than a rule that silently never runs.
|
|
126
203
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Intel macOS is not prebuilt — `cargo install lanekeep-cli` builds it from source, and both the
|
|
130
|
-
npm launcher and the Homebrew formula say so rather than failing obscurely.
|
|
204
|
+
Ten rules ship built in — four for TypeScript and JavaScript, two each for Python, Go and Rust. See
|
|
205
|
+
[`docs/built-in-rules.md`](docs/built-in-rules.md) for what each one checks and its options.
|
|
131
206
|
|
|
132
|
-
|
|
207
|
+
<details>
|
|
208
|
+
<summary>Configuring in TypeScript instead</summary>
|
|
133
209
|
|
|
134
|
-
|
|
210
|
+
`lanekeep.config.ts` still works, and is the better choice when the config computes something
|
|
211
|
+
or shares a preset across repositories — composition is then ordinary `import`, with no
|
|
212
|
+
bespoke `extends` mechanism to learn.
|
|
135
213
|
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
src/bad.ts:2:1 error [lanekeep/no-default-export] default export
|
|
141
|
-
→ use a named export, so the symbol has one name every importer must use
|
|
214
|
+
```ts
|
|
215
|
+
import { defineConfig } from 'lanekeep'
|
|
216
|
+
import noDefaultExport from 'lanekeep/no-default-export'
|
|
217
|
+
import noDebugger from './lanekeep/rules/no-debugger'
|
|
142
218
|
|
|
143
|
-
|
|
219
|
+
export default defineConfig({
|
|
220
|
+
include: ['src/**/*.{ts,tsx}'],
|
|
221
|
+
rules: [noDefaultExport, noDebugger],
|
|
222
|
+
})
|
|
144
223
|
```
|
|
145
224
|
|
|
146
|
-
|
|
225
|
+
Both formats compile to the same thing before anything reads them, so they cannot differ in
|
|
226
|
+
behavior. `lanekeep.json` wins if a project somehow has both.
|
|
147
227
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
228
|
+
</details>
|
|
229
|
+
|
|
230
|
+
## Using it
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
lanekeep check # the whole project
|
|
234
|
+
lanekeep check --staged # only what is about to be committed
|
|
235
|
+
lanekeep check --since main # only what changed against a ref
|
|
236
|
+
lanekeep check --watch # re-check on every change, until Ctrl-C
|
|
237
|
+
lanekeep check --fix # apply the safe fixes, report what is left
|
|
238
|
+
lanekeep check --profile # where the run spent its time, per rule
|
|
239
|
+
lanekeep rules # what this project has configured
|
|
240
|
+
lanekeep explain <rule-id> # one rule's card, without opening its source
|
|
151
241
|
```
|
|
152
242
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
243
|
+
`--staged` and `--since` are intersected with the config's `include`/`exclude`, and both **skip
|
|
244
|
+
cross-file rules** — a whole-corpus rule over a subset gives a wrong answer rather than a smaller
|
|
245
|
+
one, so they are skipped and named on stderr instead of quietly producing one.
|
|
156
246
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
247
|
+
**Fixes.** Only a fix its rule marked as behavior-preserving is applied. Anything else is a
|
|
248
|
+
suggestion — shown, never written — because the cautious mistake costs a manual edit and the other
|
|
249
|
+
one rewrites your code silently.
|
|
250
|
+
|
|
251
|
+
**Suppressions** carry a mandatory reason and an optional expiry. A directive that does not work
|
|
252
|
+
says so, rather than silently doing nothing:
|
|
160
253
|
|
|
161
254
|
```ts
|
|
162
255
|
// lanekeep-ignore-next-line lanekeep/no-default-export reason: legacy entry point
|
|
163
256
|
export default parse
|
|
164
257
|
```
|
|
165
258
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
```
|
|
259
|
+
Run `lanekeep check --report-unused-suppressions` to find the ones that no longer silence
|
|
260
|
+
anything.
|
|
169
261
|
|
|
170
|
-
|
|
262
|
+
**Output.** `--format` takes `human` (default), `json` (versioned, stable schema), `sarif` (GitHub
|
|
263
|
+
code scanning) and `agent` — token-minimal, grouped by rule rather than by file, with each card
|
|
264
|
+
stated once instead of once per violation. Diagnostics always go to stderr, so piping into a
|
|
265
|
+
parser works even when something fails.
|
|
171
266
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
267
|
+
**Exit codes:** `0` clean, `1` violations found, `2` the checker could not run. A caller has to be
|
|
268
|
+
able to tell "your code has problems" from "the tool is broken". `--warn-only` reports violations
|
|
269
|
+
but exits `0`, for a phased rollout.
|
|
175
270
|
|
|
176
|
-
|
|
177
|
-
the problem:
|
|
271
|
+
## In CI, editors and agents
|
|
178
272
|
|
|
179
|
-
```
|
|
180
|
-
|
|
273
|
+
```bash
|
|
274
|
+
lanekeep check --staged # pre-commit
|
|
275
|
+
lanekeep check --format sarif # GitHub code scanning
|
|
276
|
+
lanekeep server # LSP, for any editor
|
|
277
|
+
lanekeep server --protocol mcp # MCP, for an agent host
|
|
181
278
|
```
|
|
182
279
|
|
|
183
|
-
|
|
280
|
+
MCP exposes three tools — `lanekeep_check`, `lanekeep_rules`, `lanekeep_explain` — so an agent
|
|
281
|
+
can ask what it broke and what the rule wants without shelling out and parsing text.
|
|
184
282
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
$ lanekeep rules --json
|
|
188
|
-
```
|
|
283
|
+
Worked examples for each, including SARIF upload and adopting on an existing codebase, are in
|
|
284
|
+
**[CI and Editors](https://github.com/fmsouza/lanekeep/wiki/CI-and-Editors)**.
|
|
189
285
|
|
|
190
|
-
|
|
286
|
+
## How it stays fast with programmable rules
|
|
287
|
+
|
|
288
|
+
The usual problem with a native tool that runs JavaScript plugins is the boundary between them:
|
|
289
|
+
dispatching into JS once per AST node means tens of thousands of crossings per file.
|
|
290
|
+
|
|
291
|
+
lanekeep dispatches once per **query match** instead. The tree-sitter query runs in Rust across a
|
|
292
|
+
single shared parse; only matches reach your handler. That is typically two to three orders of
|
|
293
|
+
magnitude fewer crossings, and it is the reason a Rust engine still earns its place once rules are
|
|
294
|
+
TypeScript.
|
|
191
295
|
|
|
192
296
|
```
|
|
193
|
-
|
|
194
|
-
|
|
297
|
+
discover paths (globs, gitignore-aware)
|
|
298
|
+
└─> for each file, in parallel:
|
|
299
|
+
cache key ──hit──> validate tracked deps ──> cached violations + facts
|
|
300
|
+
└─miss─> path and raw-text gates reject before any parse
|
|
301
|
+
└─> parse ─> match queries in Rust
|
|
302
|
+
└─> invoke the TypeScript handler, per match only
|
|
303
|
+
└─> reduce phase: cross-file rules consume facts only, never parse trees
|
|
304
|
+
└─> filter suppressions ─> sort ─> report
|
|
195
305
|
```
|
|
196
306
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
307
|
+
A warm run with no changes executes no JavaScript at all — every file is a cache hit.
|
|
308
|
+
|
|
309
|
+
Violations are always sorted by `(ruleId, file, line, column)`, and the sandbox withholds the clock
|
|
310
|
+
and randomness, so two runs over identical input produce byte-identical output. An agent reading
|
|
311
|
+
the output twice must not see reordering as change.
|
|
312
|
+
|
|
313
|
+
## Installing without a package manager
|
|
314
|
+
|
|
315
|
+
Prebuilt for macOS on Apple silicon, Linux on x86-64 and arm64, and Windows on x86-64. The Linux
|
|
316
|
+
binaries are built against **glibc 2.17**, so they run on anything from RHEL 7 onwards.
|
|
200
317
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
318
|
+
Intel macOS is not prebuilt — `cargo install lanekeep-cli` builds it from source, and both the npm
|
|
319
|
+
launcher and the Homebrew formula say so rather than failing obscurely.
|
|
320
|
+
|
|
321
|
+
**No runtime is required to run lanekeep**, even though rules are written in TypeScript. Node,
|
|
322
|
+
Python or Go is needed only to install it from that ecosystem, where it picks which binary to
|
|
323
|
+
fetch. Nothing is pulled in as a dependency any of those ways.
|
|
324
|
+
|
|
325
|
+
The Go package is a small launcher, because Go can only install and pin things written in Go: it
|
|
326
|
+
fetches the real binary on first use, verifies it against the release's published checksums, and
|
|
327
|
+
caches it. Set `LANEKEEP_BINARY` to an already-installed lanekeep and it fetches nothing.
|
|
207
328
|
|
|
208
329
|
## Documentation
|
|
209
330
|
|
|
331
|
+
**The [wiki](https://github.com/fmsouza/lanekeep/wiki) is the place to start** — it is task-shaped and organized by language.
|
|
332
|
+
|
|
333
|
+
| Page | Purpose |
|
|
334
|
+
| --- | --- |
|
|
335
|
+
| [Getting Started](https://github.com/fmsouza/lanekeep/wiki/Getting-Started) | Install and catch something, in about a minute |
|
|
336
|
+
| [Configuration](https://github.com/fmsouza/lanekeep/wiki/Configuration) | `lanekeep.json`, every field |
|
|
337
|
+
| [Writing Rules](https://github.com/fmsouza/lanekeep/wiki/Writing-Rules) | Rule anatomy and the full host API |
|
|
338
|
+
| [CI and Editors](https://github.com/fmsouza/lanekeep/wiki/CI-and-Editors) | Pre-commit, GitHub Actions, LSP, MCP |
|
|
339
|
+
| [Go](https://github.com/fmsouza/lanekeep/wiki/Go) · [Python](https://github.com/fmsouza/lanekeep/wiki/Python) · [Rust](https://github.com/fmsouza/lanekeep/wiki/Rust) · [TypeScript and JavaScript](https://github.com/fmsouza/lanekeep/wiki/TypeScript-and-JavaScript) | Per-language guides |
|
|
340
|
+
|
|
341
|
+
In-repo, versioned with the code:
|
|
342
|
+
|
|
210
343
|
| Document | Purpose |
|
|
211
344
|
| --- | --- |
|
|
212
345
|
| [`docs/architecture.md`](docs/architecture.md) | The full design: execution model, host API, cache, milestones |
|
|
213
346
|
| [`docs/built-in-rules.md`](docs/built-in-rules.md) | The rules lanekeep ships with, and their options |
|
|
214
347
|
| [`docs/cross-file-rules.md`](docs/cross-file-rules.md) | Writing a rule that needs a whole-corpus view |
|
|
215
|
-
| [`
|
|
348
|
+
| [`docs/adr/`](docs/adr/) | Decision records: why the design is the way it is |
|
|
216
349
|
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Setup, commands, and the pull request process |
|
|
350
|
+
| [`AGENTS.md`](AGENTS.md) | How to work in this repository — for coding agents and humans alike |
|
|
217
351
|
| [`SECURITY.md`](SECURITY.md) | Threat model and how to report a vulnerability |
|
|
218
352
|
| [`docs/releasing.md`](docs/releasing.md) | How a release is built, gated and published |
|
|
353
|
+
| [`CHANGELOG.md`](CHANGELOG.md) | What changed, per release |
|
|
219
354
|
|
|
220
355
|
## Security
|
|
221
356
|
|
|
@@ -228,10 +363,10 @@ Rules are executable code, so the posture is about confinement rather than absen
|
|
|
228
363
|
- **No network access.** Ever, in any mode, with no configuration that enables it.
|
|
229
364
|
- **Filesystem confinement.** Reads go through a tracked `ctx.readFile`, confined to the project
|
|
230
365
|
root. Writes happen only under `--fix`, only to matched files, only within reported ranges.
|
|
231
|
-
- **Bounded execution.** A per-invocation timeout, a
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
366
|
+
- **Bounded execution.** A per-invocation timeout, a global run budget and a per-runtime memory
|
|
367
|
+
ceiling, none disableable — a rule that hangs a pre-commit hook is indistinguishable from a
|
|
368
|
+
broken tool. Breaching any of them cancels the run and exits `2`, rather than reporting a partial
|
|
369
|
+
result as a clean one.
|
|
235
370
|
- **Deterministic by construction.** The sandbox withholds the clock and randomness, so a rule
|
|
236
371
|
cannot introduce nondeterminism even by accident.
|
|
237
372
|
|
|
@@ -239,14 +374,33 @@ This bounds blast radius and makes third-party rule sets reviewable. It is not a
|
|
|
239
374
|
someone who can already commit to the repository being checked. To report a vulnerability, see
|
|
240
375
|
[`SECURITY.md`](SECURITY.md).
|
|
241
376
|
|
|
377
|
+
## Project status
|
|
378
|
+
|
|
379
|
+
**Released and usable.** The current version is on [crates.io](https://crates.io/crates/lanekeep-cli),
|
|
380
|
+
[npm](https://www.npmjs.com/package/lanekeep), [PyPI](https://pypi.org/project/lanekeep/), Homebrew,
|
|
381
|
+
and as a Go module — one build feeding every channel, so the bytes are identical whichever you use.
|
|
382
|
+
|
|
383
|
+
It is **0.x**, and this repository treats that as semver does: a minor bump may break a public Rust
|
|
384
|
+
API. Rule authors are insulated from that — `ctx` methods and the config shape are additive — but
|
|
385
|
+
pin a version if you embed the crates.
|
|
386
|
+
|
|
387
|
+
Known gaps, stated rather than implied:
|
|
388
|
+
|
|
389
|
+
- **No editor types for rule authors yet** (above).
|
|
390
|
+
- **The performance budgets in [`docs/architecture.md`](docs/architecture.md) §15 are not met.**
|
|
391
|
+
They are targets, and that document says by how much and what the levers are. The tool is fast;
|
|
392
|
+
the numbers are simply ambitious.
|
|
393
|
+
- **No type-aware analysis**, by design. Binding resolution is syntactic — see §1 non-goals.
|
|
394
|
+
|
|
242
395
|
## Contributing
|
|
243
396
|
|
|
244
|
-
Contributions are welcome, particularly new built-in rules and new host API surface. Start
|
|
245
|
-
|
|
246
|
-
|
|
397
|
+
Contributions are welcome, particularly new built-in rules and new host API surface. Start with
|
|
398
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) — `./scripts/setup-dev.sh` installs everything and wires the
|
|
399
|
+
git hooks, and `just check` is the same gate CI runs.
|
|
247
400
|
|
|
248
|
-
All work ships as squashed pull requests with
|
|
249
|
-
titles. `main` is protected and takes
|
|
401
|
+
All work ships as squashed pull requests with
|
|
402
|
+
[Conventional Commits](https://www.conventionalcommits.org/) titles. `main` is protected and takes
|
|
403
|
+
no direct pushes.
|
|
250
404
|
|
|
251
405
|
## License
|
|
252
406
|
|
package/builtin.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types reached through the `typesVersions` mapping in `package.json`.
|
|
3
|
+
*
|
|
4
|
+
* That mapping points *every* specifier here, the bare `lanekeep` included — TypeScript's
|
|
5
|
+
* `"*"` pattern does not exclude the package root, and a narrower pattern would have to
|
|
6
|
+
* predict what future built-ins are called. So this file is a superset: it re-exports
|
|
7
|
+
* everything `index.d.ts` has, and adds the default export a built-in subpath needs.
|
|
8
|
+
*
|
|
9
|
+
* A `declare module 'lanekeep/*'` block inside `index.d.ts` would have been the obvious way
|
|
10
|
+
* to do this and does nothing at all: a `declare module` inside a file that has its own
|
|
11
|
+
* imports or exports is module augmentation, not an ambient declaration, so TypeScript
|
|
12
|
+
* ignores it and the import stays unresolved. That failed silently until a compile test
|
|
13
|
+
* caught it.
|
|
14
|
+
*
|
|
15
|
+
* The default covers both shapes a built-in can take, because which one it is cannot be known
|
|
16
|
+
* from the specifier: a rule taking options is a factory — `noRestrictedImports({ ... })` —
|
|
17
|
+
* and one taking none is the rule itself.
|
|
18
|
+
*/
|
|
19
|
+
export * from './index'
|
|
20
|
+
|
|
21
|
+
import type { Rule } from './index'
|
|
22
|
+
|
|
23
|
+
declare const rule: Rule & ((options?: Record<string, unknown>) => Rule)
|
|
24
|
+
export default rule
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for authoring lanekeep rules.
|
|
3
|
+
*
|
|
4
|
+
* These describe the host API a rule reaches inside lanekeep's sandbox. Nothing here runs in
|
|
5
|
+
* Node: `defineRule` and `defineConfig` are identity functions whose only job is to give the
|
|
6
|
+
* compiler something to check against, and `RuleContext` is provided by lanekeep at run time.
|
|
7
|
+
*
|
|
8
|
+
* Every member below is asserted against the host's own registration in
|
|
9
|
+
* `crates/lanekeep-js/tests/host_types.rs`. A method that exists here and not there — or the
|
|
10
|
+
* reverse — fails that test, because a definition that drifts from the engine is worse than
|
|
11
|
+
* none: it produces confident autocomplete for something that does not exist.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A node in the parse tree.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately opaque. Nodes cross into the sandbox as integer handles rather than objects,
|
|
18
|
+
* which is one of the one-way doors in the architecture — and the reason this is a branded
|
|
19
|
+
* type rather than `number` is that **the root node's handle is `0`**. Written as a plain
|
|
20
|
+
* number, `if (!node)` looks like a null check and silently discards the root, which is how
|
|
21
|
+
* a rule loses its whole top-level case without any error.
|
|
22
|
+
*
|
|
23
|
+
* Compare against `undefined` explicitly.
|
|
24
|
+
*/
|
|
25
|
+
export type Node = number & { readonly __lanekeepNode: unique symbol }
|
|
26
|
+
|
|
27
|
+
/** How a name was introduced, as `ctx.bindingKind` reports it. */
|
|
28
|
+
export type BindingKind =
|
|
29
|
+
| 'import'
|
|
30
|
+
| 'const'
|
|
31
|
+
| 'let'
|
|
32
|
+
| 'var'
|
|
33
|
+
| 'param'
|
|
34
|
+
| 'function'
|
|
35
|
+
| 'class'
|
|
36
|
+
| 'catch-param'
|
|
37
|
+
| 'assignment'
|
|
38
|
+
| 'loop'
|
|
39
|
+
| 'context-manager'
|
|
40
|
+
| 'comprehension'
|
|
41
|
+
| 'type'
|
|
42
|
+
| 'receiver'
|
|
43
|
+
| 'type-param'
|
|
44
|
+
| 'module'
|
|
45
|
+
| 'trait'
|
|
46
|
+
|
|
47
|
+
/** A language lanekeep can parse. */
|
|
48
|
+
export type LanguageId = 'typescript' | 'tsx' | 'javascript' | 'python' | 'go' | 'rust'
|
|
49
|
+
|
|
50
|
+
/** How serious a violation is. */
|
|
51
|
+
export type Severity = 'error' | 'warn' | 'off'
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The captures of one query match, keyed by capture name without the `@`.
|
|
55
|
+
*
|
|
56
|
+
* A capture that did not participate in the match is absent, which is why the values are
|
|
57
|
+
* optional — an alternation like `[(a) (b)] @x` binds `@x` either way, but two separate
|
|
58
|
+
* patterns capturing different names do not.
|
|
59
|
+
*/
|
|
60
|
+
export type Match = Record<string, Node | undefined>
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* What a rule tells whoever has to act on the violation — increasingly an agent.
|
|
64
|
+
*
|
|
65
|
+
* Not documentation, and not optional. `remediation` is the field worth the effort: it should
|
|
66
|
+
* say what to do, not restate the problem.
|
|
67
|
+
*/
|
|
68
|
+
export interface RuleCard {
|
|
69
|
+
/** What is wrong, in a few words. */
|
|
70
|
+
message: string
|
|
71
|
+
/** What to do about it. */
|
|
72
|
+
remediation: string
|
|
73
|
+
/** One example each way. */
|
|
74
|
+
examples: {
|
|
75
|
+
bad: string
|
|
76
|
+
good: string
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Cheap rejections applied before a file is parsed. */
|
|
81
|
+
export interface Gates {
|
|
82
|
+
/**
|
|
83
|
+
* Literal substrings a file's raw bytes must contain. A file missing any one of them is
|
|
84
|
+
* never parsed.
|
|
85
|
+
*
|
|
86
|
+
* **This is an *and*, not an *or*.** A rule matching either of two tokens cannot express
|
|
87
|
+
* its gate as `['a', 'b']` — that rejects any file containing only one, which is usually
|
|
88
|
+
* most of them, and the rule then reports nothing while looking healthy. There is no `or`
|
|
89
|
+
* form; omit the gate when no single substring covers every case.
|
|
90
|
+
*/
|
|
91
|
+
fileContains?: string[]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A replacement a rule offers for a violation. */
|
|
95
|
+
export interface Fix {
|
|
96
|
+
/** The node whose text is replaced. */
|
|
97
|
+
node: Node
|
|
98
|
+
/** What to replace it with. */
|
|
99
|
+
text: string
|
|
100
|
+
/**
|
|
101
|
+
* Whether the fix preserves behavior.
|
|
102
|
+
*
|
|
103
|
+
* Only a fix marked `true` is applied by `--fix`. Anything else is a suggestion — shown,
|
|
104
|
+
* never written — because the cautious mistake costs a manual edit and the other one
|
|
105
|
+
* rewrites someone's code silently.
|
|
106
|
+
*/
|
|
107
|
+
safe?: boolean
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Options for a single report. */
|
|
111
|
+
export interface ReportOptions {
|
|
112
|
+
/** Overrides the card's `message` for this one violation. */
|
|
113
|
+
message?: string
|
|
114
|
+
/** A replacement to offer. */
|
|
115
|
+
fix?: Fix
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A fact a rule emits for the reduce phase.
|
|
120
|
+
*
|
|
121
|
+
* `kind` is required and must be non-empty, because it is what `ctx.facts('...')` filters on.
|
|
122
|
+
* A fact without one could never be retrieved, so emitting it is always a mistake — and a
|
|
123
|
+
* silent one, since the rule would look like it was working right up until `reduce` found
|
|
124
|
+
* nothing. lanekeep throws rather than accept it.
|
|
125
|
+
*/
|
|
126
|
+
export interface Fact {
|
|
127
|
+
kind: string
|
|
128
|
+
[key: string]: unknown
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A fact as `reduce` receives it, with the file that emitted it. */
|
|
132
|
+
export interface EmittedFact extends Fact {
|
|
133
|
+
/** Path of the file this came from, relative to the project root. */
|
|
134
|
+
file: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** What a rule's `check` handler reaches. */
|
|
138
|
+
export interface RuleContext {
|
|
139
|
+
/** Path of the file being checked, relative to the project root. */
|
|
140
|
+
readonly filePath: string
|
|
141
|
+
/** The whole file, as text. */
|
|
142
|
+
readonly fileText: string
|
|
143
|
+
/** The tree's root node. Its handle is `0` — see {@link Node}. */
|
|
144
|
+
readonly root: Node
|
|
145
|
+
|
|
146
|
+
/** The node's kind, as tree-sitter names it. */
|
|
147
|
+
kind(node: Node): string
|
|
148
|
+
/** The source text the node spans. */
|
|
149
|
+
text(node: Node): string
|
|
150
|
+
/** Whether this is a named node rather than an anonymous token. */
|
|
151
|
+
isNamed(node: Node): boolean
|
|
152
|
+
/** One-based line of the node's start. */
|
|
153
|
+
line(node: Node): number
|
|
154
|
+
/** One-based column of the node's start. */
|
|
155
|
+
column(node: Node): number
|
|
156
|
+
|
|
157
|
+
/** The node's parent, or `undefined` at the root. */
|
|
158
|
+
parent(node: Node): Node | undefined
|
|
159
|
+
/** Every child, including anonymous tokens. */
|
|
160
|
+
children(node: Node): Node[]
|
|
161
|
+
/** Named children only. */
|
|
162
|
+
namedChildren(node: Node): Node[]
|
|
163
|
+
/** Every ancestor, innermost first. */
|
|
164
|
+
ancestors(node: Node): Node[]
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Whether an identifier resolves to a given import.
|
|
168
|
+
*
|
|
169
|
+
* Handles aliasing, so `import { makeStyles as ms }` resolves correctly. This is the call
|
|
170
|
+
* that separates a rule from a grep: a text match both misses the alias and fires on a
|
|
171
|
+
* local of the same name.
|
|
172
|
+
*
|
|
173
|
+
* @param name Which export. Omit to match the module regardless of which name was taken.
|
|
174
|
+
*/
|
|
175
|
+
resolvesToImport(node: Node, module: string, name?: string): boolean
|
|
176
|
+
/** Whether an identifier came from a module matching this glob. */
|
|
177
|
+
isImportedFrom(node: Node, pattern: string): boolean
|
|
178
|
+
/** How the name was introduced, or `undefined` when it does not resolve. */
|
|
179
|
+
bindingKind(node: Node): BindingKind | undefined
|
|
180
|
+
/** Whether an outer binding of the same name is hidden by this one. */
|
|
181
|
+
isShadowed(node: Node): boolean
|
|
182
|
+
|
|
183
|
+
/** Run a query inside a subtree. */
|
|
184
|
+
querySubtree(node: Node, query: string): Match[]
|
|
185
|
+
/** The nearest ancestor matching a query, with its captures. */
|
|
186
|
+
closestAncestor(node: Node, query: string): Match | undefined
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Read another file, relative to the project root.
|
|
190
|
+
*
|
|
191
|
+
* Tracked: the read becomes part of the cache key, so a change to that file invalidates
|
|
192
|
+
* this one's result. Confined to the project root; `undefined` when absent or outside.
|
|
193
|
+
*/
|
|
194
|
+
readFile(path: string): string | undefined
|
|
195
|
+
/** Whether a file exists, tracked the same way. */
|
|
196
|
+
fileExists(path: string): boolean
|
|
197
|
+
|
|
198
|
+
/** Emit a fact for the reduce phase. */
|
|
199
|
+
emitFact(fact: Fact): void
|
|
200
|
+
/** Facts emitted so far, optionally filtered by `kind`. */
|
|
201
|
+
facts(kind?: string): EmittedFact[]
|
|
202
|
+
|
|
203
|
+
/** Report a violation at a node. */
|
|
204
|
+
report(at: Node, message?: string | ReportOptions): void
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** A violation the reduce phase reports, which has no node to point at. */
|
|
208
|
+
export interface ReduceLocation {
|
|
209
|
+
/** Path relative to the project root. */
|
|
210
|
+
file: string
|
|
211
|
+
/** One-based. */
|
|
212
|
+
line?: number
|
|
213
|
+
/** One-based. */
|
|
214
|
+
column?: number
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* What a rule's `reduce` handler reaches.
|
|
219
|
+
*
|
|
220
|
+
* Deliberately smaller than {@link RuleContext}: **the reduce phase never touches parse
|
|
221
|
+
* trees.** Facts are small and serializable, which is what keeps cross-file rules parallel
|
|
222
|
+
* and cacheable — handing a tree to `reduce` would make the whole corpus resident.
|
|
223
|
+
*/
|
|
224
|
+
export interface ReduceContext {
|
|
225
|
+
/** Every file the run checked, relative to the project root. */
|
|
226
|
+
readonly files: string[]
|
|
227
|
+
/** Facts from every file, optionally filtered by `kind`. */
|
|
228
|
+
facts(kind?: string): EmittedFact[]
|
|
229
|
+
/** Report a violation against a file. */
|
|
230
|
+
report(at: ReduceLocation, message?: string | ReportOptions): void
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** A rule, as `defineRule` takes it. */
|
|
234
|
+
export interface Rule {
|
|
235
|
+
/**
|
|
236
|
+
* Namespaced identifier, as `namespace/name`.
|
|
237
|
+
*
|
|
238
|
+
* `local/` needs no declaration and `lanekeep/` is reserved for built-ins; any other
|
|
239
|
+
* namespace must be listed in the config's `namespaces`.
|
|
240
|
+
*/
|
|
241
|
+
id: string
|
|
242
|
+
/**
|
|
243
|
+
* Which languages this rule applies to.
|
|
244
|
+
*
|
|
245
|
+
* **Defaults to `['typescript', 'tsx']`**, and this is the field most worth getting right
|
|
246
|
+
* on a rule for anything else. The grammar is chosen by the file, not by the rule, and a
|
|
247
|
+
* rule does not run on a file whose language it does not name — so omitting this on a Go
|
|
248
|
+
* or Rust rule means it silently never fires.
|
|
249
|
+
*/
|
|
250
|
+
language?: LanguageId | LanguageId[]
|
|
251
|
+
/** How serious a violation is, before any config override. */
|
|
252
|
+
severity: Severity
|
|
253
|
+
/** What the rule tells whoever has to act on it. */
|
|
254
|
+
card: RuleCard
|
|
255
|
+
/** Cheap rejections before parsing. */
|
|
256
|
+
gates?: Gates
|
|
257
|
+
/**
|
|
258
|
+
* The tree-sitter query gating the handler.
|
|
259
|
+
*
|
|
260
|
+
* Rust matches it across a single shared parse and only matches reach `check`, which is
|
|
261
|
+
* what keeps a JavaScript rule affordable. Write the narrowest query that captures what
|
|
262
|
+
* you need; `check` then only refines.
|
|
263
|
+
*/
|
|
264
|
+
query: string
|
|
265
|
+
/** A per-invocation budget overriding the default, in milliseconds. */
|
|
266
|
+
timeout?: number
|
|
267
|
+
/** Called once per query match. */
|
|
268
|
+
check?(ctx: RuleContext, match: Match): void
|
|
269
|
+
/** Called once per run, after every file, with facts only. */
|
|
270
|
+
reduce?(ctx: ReduceContext): void
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** A lanekeep configuration, as `defineConfig` takes it. */
|
|
274
|
+
export interface Config {
|
|
275
|
+
/** Globs selecting files to check, relative to the project root. */
|
|
276
|
+
include?: string[]
|
|
277
|
+
/** Globs removing files from that selection. */
|
|
278
|
+
exclude?: string[]
|
|
279
|
+
/** Rule-id namespaces this project uses beyond `local`. */
|
|
280
|
+
namespaces?: string[]
|
|
281
|
+
/** Override a rule's own severity, by id. */
|
|
282
|
+
severity?: Record<string, Severity>
|
|
283
|
+
/** Execution budgets, in milliseconds. */
|
|
284
|
+
timeouts?: {
|
|
285
|
+
/** Per rule invocation. */
|
|
286
|
+
rule?: number
|
|
287
|
+
/** Wall-clock, for the whole run. */
|
|
288
|
+
global?: number
|
|
289
|
+
}
|
|
290
|
+
/** The rules to run, in order. */
|
|
291
|
+
rules: Rule[]
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Define a rule.
|
|
296
|
+
*
|
|
297
|
+
* An identity function. It exists so the compiler checks the object against {@link Rule}
|
|
298
|
+
* where it is written, rather than reporting a mismatch from wherever it is imported.
|
|
299
|
+
*/
|
|
300
|
+
export declare function defineRule(rule: Rule): Rule
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Define a configuration.
|
|
304
|
+
*
|
|
305
|
+
* An identity function, for the same reason as {@link defineRule}. Most projects will write
|
|
306
|
+
* `lanekeep.json` instead — configuration is data, and only rules need to be programs.
|
|
307
|
+
*/
|
|
308
|
+
export declare function defineConfig(config: Config): Config
|
package/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The runtime half of the authoring package.
|
|
2
|
+
//
|
|
3
|
+
// Rules never execute in Node — lanekeep evaluates them in its own sandbox, where `lanekeep`
|
|
4
|
+
// resolves to a host module rather than to this file. These exports exist so that a tool
|
|
5
|
+
// which *does* load a rule under Node (a bundler, a test runner, an editor's type server
|
|
6
|
+
// following the import) finds something coherent instead of a missing module.
|
|
7
|
+
//
|
|
8
|
+
// Identity functions, which is also what they are inside the sandbox: their entire job is to
|
|
9
|
+
// give the compiler a place to check the object against a type.
|
|
10
|
+
'use strict'
|
|
11
|
+
|
|
12
|
+
const identity = (value) => value
|
|
13
|
+
|
|
14
|
+
module.exports = { defineRule: identity, defineConfig: identity }
|
|
15
|
+
module.exports.default = module.exports
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lanekeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Deterministic, AST-based architectural conformance checking",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -14,15 +14,27 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"bin/lanekeep",
|
|
16
16
|
"resolve.js",
|
|
17
|
+
"index.js",
|
|
18
|
+
"index.d.ts",
|
|
19
|
+
"builtin.d.ts",
|
|
17
20
|
"README.md"
|
|
18
21
|
],
|
|
19
22
|
"engines": {
|
|
20
23
|
"node": ">=18"
|
|
21
24
|
},
|
|
22
25
|
"optionalDependencies": {
|
|
23
|
-
"@lanekeep/darwin-arm64": "0.
|
|
24
|
-
"@lanekeep/linux-arm64": "0.
|
|
25
|
-
"@lanekeep/linux-x64": "0.
|
|
26
|
-
"@lanekeep/win32-x64": "0.
|
|
26
|
+
"@lanekeep/darwin-arm64": "0.6.0",
|
|
27
|
+
"@lanekeep/linux-arm64": "0.6.0",
|
|
28
|
+
"@lanekeep/linux-x64": "0.6.0",
|
|
29
|
+
"@lanekeep/win32-x64": "0.6.0"
|
|
30
|
+
},
|
|
31
|
+
"main": "index.js",
|
|
32
|
+
"types": "index.d.ts",
|
|
33
|
+
"typesVersions": {
|
|
34
|
+
"*": {
|
|
35
|
+
"*": [
|
|
36
|
+
"builtin.d.ts"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
27
39
|
}
|
|
28
40
|
}
|