lanekeep 0.4.0 → 0.5.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.
Files changed (2) hide show
  1. package/README.md +284 -133
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -2,220 +2,352 @@
2
2
 
3
3
  **Deterministic, AST-based architectural conformance checking for AI-generated and human-written code.**
4
4
 
5
+ [![crates.io](https://img.shields.io/crates/v/lanekeep-cli?label=crates.io)](https://crates.io/crates/lanekeep-cli)
6
+ [![npm](https://img.shields.io/npm/v/lanekeep?label=npm)](https://www.npmjs.com/package/lanekeep)
7
+ [![PyPI](https://img.shields.io/pypi/v/lanekeep?label=pypi)](https://pypi.org/project/lanekeep/)
8
+ [![CI](https://github.com/fmsouza/lanekeep/actions/workflows/ci.yml/badge.svg)](https://github.com/fmsouza/lanekeep/actions/workflows/ci.yml)
5
9
  [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)
6
10
 
7
- > **Status: early development.** Nothing is released yet and the CLI described below is not
8
- > usable. The architecture is settled see [`docs/architecture.md`](docs/architecture.md) and
9
- > the work is tracked as a sequence of milestones. Do not depend on this yet.
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
- ## What it is
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:**
45
+
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.
59
+
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
+ ```
72
+
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
+ ---
14
79
 
15
- lanekeep is not a linter in the ESLint sense. ESLint enforces language-level correctness.
16
- lanekeep enforces *project-specific conventions* — the ones a language model has no way to infer
17
- from the code it is shown, because they live in your team's heads and your reviewers' comments.
80
+ ## What it is
18
81
 
19
- Every rule is a codified answer to **"the agent keeps doing this wrong."**
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.
20
85
 
21
- Rules are TypeScript programs, written in the same language as the code they inspect:
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-numeric-sizes',
92
+ id: 'local/no-fmt-println',
93
+ language: 'go',
28
94
  severity: 'error',
29
95
 
30
96
  card: {
31
- message: 'Literal numeric size inside makeStyles',
32
- remediation: 'Use theme.spacing.*, theme.borderRadius.* or theme.borders.*',
33
- examples: { bad: 'padding: 12', good: 'padding: theme.spacing.md' },
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
- (pair
39
- key: (property_identifier) @prop
40
- value: [(number) (unary_expression operand: (number))] @value) @match
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 (!/^(padding|margin|gap|borderRadius)/.test(ctx.text(m.prop))) return
45
- if (Number(ctx.text(m.value)) === 0) return
114
+ if (ctx.text(m.pkg) !== 'fmt') return
115
+ if (ctx.text(m.fn) !== 'Println') return
46
116
 
47
- const call = ctx.closestAncestor(m.match, '(call_expression function: (identifier) @f)')
48
- if (!call) return
49
- if (!ctx.resolvesToImport(call.f, { module: '@rneui/themed', name: 'makeStyles' })) return
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.match)
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
- ## Why it exists
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
- An agent that writes code against your codebase will violate your conventions confidently and
63
- repeatedly, because those conventions are invisible in the code it was shown. Telling it again in
64
- the next prompt does not scale. Encoding the convention as a rule does.
141
+ > **Editor types are not shipped yet.** `defineRule` and `defineConfig` resolve inside lanekeep's
142
+ > sandbox at run time, so rules execute correctly, but there is no published package supplying
143
+ > TypeScript definitions for the host API you will not get autocomplete on `ctx` today.
144
+ > [`docs/architecture.md`](docs/architecture.md) §6 documents the full surface in the meantime.
65
145
 
66
- That makes the design constraints unusual for a static analyzer:
146
+ ## Supported languages
67
147
 
68
- - **It runs in the inner loop.** Agents and developers invoke it after every edit, so a cold run
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.
148
+ Each guide covers installing lanekeep in that ecosystem, what to put in the config, which
149
+ built-in rules apply, a worked custom rule, and the resolution behavior specific to it.
74
150
 
75
- ## How it stays fast with programmable rules
151
+ | Language | Guide | Extensions |
152
+ | --- | --- | --- |
153
+ | Go | **[Go guide](https://github.com/fmsouza/lanekeep/wiki/Go)** | `.go` |
154
+ | Python | **[Python guide](https://github.com/fmsouza/lanekeep/wiki/Python)** | `.py`, `.pyi` |
155
+ | Rust | **[Rust guide](https://github.com/fmsouza/lanekeep/wiki/Rust)** | `.rs` |
156
+ | TypeScript / JavaScript | **[TypeScript and JavaScript guide](https://github.com/fmsouza/lanekeep/wiki/TypeScript-and-JavaScript)** | `.ts`, `.mts`, `.cts`, `.tsx`, `.js`, `.mjs`, `.cjs`, `.jsx` |
76
157
 
77
- The usual problem with a native tool that runs JavaScript plugins is the boundary between them:
78
- dispatching into JS once per AST node means tens of thousands of crossings per file.
158
+ Every one carries syntactic binding resolution, so a rule can ask where a name came from rather
159
+ than matching text `ctx.bindingKind`, `ctx.resolvesToImport` and `ctx.isShadowed` answer for
160
+ all of them.
79
161
 
80
- lanekeep dispatches once per **query match** instead. The tree-sitter query runs in Rust across a
81
- single shared parse; only matches reach your handler. That is typically two to three orders of
82
- magnitude fewer crossings, and it is the reason a Rust engine still earns its place once rules are
83
- TypeScript.
162
+ **The grammar is chosen by the file, not by the rule.** A rule declares which languages it
163
+ applies to and does not run on files of any other, defaulting to `['typescript', 'tsx']` when it
164
+ says nothing. That default is the one thing to get right on a non-TypeScript rule: omit
165
+ `language` on a Go rule and it silently never fires.
84
166
 
85
- ```
86
- discover paths (globs, gitignore-aware)
87
- └─> for each file, in parallel:
88
- cache key ──hit──> validate tracked deps ──> cached violations + facts
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
- ```
167
+ ## Configuration
95
168
 
96
- A warm run with no changes executes no JavaScript at all every file is a cache hit.
169
+ `lanekeep.json`, at the project root. `lanekeep init` writes one for you, matched to the
170
+ project it finds.
97
171
 
98
- ## Installation
172
+ ```json
173
+ {
174
+ "$schema": "https://raw.githubusercontent.com/fmsouza/lanekeep/main/schema/lanekeep.schema.json",
99
175
 
100
- Pick whichever matches the project you are adding it to:
176
+ "include": ["**/*.go"],
177
+ "exclude": ["**/*_test.go"],
101
178
 
102
- ```bash
103
- npm install --save-dev lanekeep # Node
104
- pip install lanekeep # Python
105
- brew install fmsouza/tap/lanekeep # macOS and Linux, system-wide
106
- cargo install lanekeep-cli # from source
179
+ "rules": [
180
+ "lanekeep/no-package-init",
181
+ { "rule": "lanekeep/no-restricted-imports", "options": { "restrictions": [
182
+ { "module": "database/sql", "from": ["!internal/store/**"], "reason": "go through the store package" }
183
+ ] } },
184
+ "./lanekeep/rules/no-fmt-println.ts"
185
+ ]
186
+ }
107
187
  ```
108
188
 
109
- For a Go project, pin it in `go.mod` alongside your other tools:
189
+ A string uses a rule as it comes; the object form calls it with options. `$schema` is what
190
+ gives you **completion and validation in your editor with nothing installed** — VS Code and
191
+ most others read it directly.
110
192
 
111
- ```bash
112
- go get -tool github.com/fmsouza/lanekeep/cmd/lanekeep
113
- ```
193
+ **Rules are TypeScript, configuration is not.** A rule is a program, and that is the point of
194
+ the tool; saying which rules to run is data. A Go or Python team should not have to write a
195
+ `.ts` file to do the second, which is why the config is JSON and only the rules are not.
114
196
 
115
- then `go tool lanekeep check ./...`. Go can only install and pin things written in Go, so
116
- that package is a small launcher which fetches the real binary on first use, verifies it
117
- against the release's published checksums, and caches it. Set `LANEKEEP_BINARY` to an
118
- already-installed lanekeep and it fetches nothing.
197
+ Rule ids are namespaced. `lanekeep/` is reserved for built-ins and `local/` needs no
198
+ declaration; any other prefix must be listed in `namespaces`, so a typo in an id is an error
199
+ rather than a rule that silently never runs.
119
200
 
120
- Or download a binary from the [releases page](https://github.com/fmsouza/lanekeep/releases).
201
+ Ten rules ship built in four for TypeScript and JavaScript, two each for Python, Go and Rust. See
202
+ [`docs/built-in-rules.md`](docs/built-in-rules.md) for what each one checks and its options.
121
203
 
122
- A single static binary with the JavaScript engine compiled in. **No runtime is required to run
123
- lanekeep**, even though rules are written in TypeScript — Node, Python or Go is needed only to
124
- install it from that ecosystem, where it picks which binary to fetch. Nothing is pulled in as a
125
- dependency any of those ways.
204
+ <details>
205
+ <summary>Configuring in TypeScript instead</summary>
126
206
 
127
- Prebuilt for macOS on Apple silicon, Linux on x86-64 and arm64, and Windows on x86-64. The
128
- Linux binaries are built against glibc 2.17, so they run on anything from RHEL 7 onwards.
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.
207
+ `lanekeep.config.ts` still works, and is the better choice when the config computes something
208
+ or shares a preset across repositories composition is then ordinary `import`, with no
209
+ bespoke `extends` mechanism to learn.
131
210
 
132
- See [`docs/releasing.md`](docs/releasing.md) for how a release is cut.
133
-
134
- ## What it looks like
211
+ ```ts
212
+ import { defineConfig } from 'lanekeep'
213
+ import noDefaultExport from 'lanekeep/no-default-export'
214
+ import noDebugger from './lanekeep/rules/no-debugger'
135
215
 
216
+ export default defineConfig({
217
+ include: ['src/**/*.{ts,tsx}'],
218
+ rules: [noDefaultExport, noDebugger],
219
+ })
136
220
  ```
137
- $ lanekeep check
138
- src/also.ts:2:1 error [lanekeep/no-default-export] default export
139
- → use a named export, so the symbol has one name every importer must use
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
142
221
 
143
- 2 error(s) across 2 file(s) checked
144
- ```
222
+ Both formats compile to the same thing before anything reads them, so they cannot differ in
223
+ behavior. `lanekeep.json` wins if a project somehow has both.
145
224
 
146
- Rules may offer a fix, applied with `--fix`:
225
+ </details>
147
226
 
148
- ```
149
- $ lanekeep check --fix
150
- fixed 2 violation(s) in 2 file(s)
227
+ ## Using it
228
+
229
+ ```bash
230
+ lanekeep check # the whole project
231
+ lanekeep check --staged # only what is about to be committed
232
+ lanekeep check --since main # only what changed against a ref
233
+ lanekeep check --watch # re-check on every change, until Ctrl-C
234
+ lanekeep check --fix # apply the safe fixes, report what is left
235
+ lanekeep check --profile # where the run spent its time, per rule
236
+ lanekeep rules # what this project has configured
237
+ lanekeep explain <rule-id> # one rule's card, without opening its source
151
238
  ```
152
239
 
153
- Only fixes a rule marked as behavior-preserving are applied. Anything else is a suggestion —
154
- shown, never written because the cautious mistake costs a manual edit and the other one
155
- rewrites your code silently.
240
+ `--staged` and `--since` are intersected with the config's `include`/`exclude`, and both **skip
241
+ cross-file rules**a whole-corpus rule over a subset gives a wrong answer rather than a smaller
242
+ one, so they are skipped and named on stderr instead of quietly producing one.
156
243
 
157
- Suppressions carry a mandatory reason and an optional expiry, and a directive that does not
158
- work says so a missing reason, a bare rule id, or an unreadable date is reported rather
159
- than silently doing nothing:
244
+ **Fixes.** Only a fix its rule marked as behavior-preserving is applied. Anything else is a
245
+ suggestion shown, never written because the cautious mistake costs a manual edit and the other
246
+ one rewrites your code silently.
247
+
248
+ **Suppressions** carry a mandatory reason and an optional expiry. A directive that does not work
249
+ says so, rather than silently doing nothing:
160
250
 
161
251
  ```ts
162
252
  // lanekeep-ignore-next-line lanekeep/no-default-export reason: legacy entry point
163
253
  export default parse
164
254
  ```
165
255
 
166
- ```
167
- $ lanekeep check --report-unused-suppressions
168
- ```
256
+ Run `lanekeep check --report-unused-suppressions` to find the ones that no longer silence
257
+ anything.
169
258
 
170
- To start from nothing:
259
+ **Output.** `--format` takes `human` (default), `json` (versioned, stable schema), `sarif` (GitHub
260
+ code scanning) and `agent` — token-minimal, grouped by rule rather than by file, with each card
261
+ stated once instead of once per violation. Diagnostics always go to stderr, so piping into a
262
+ parser works even when something fails.
171
263
 
172
- ```
173
- $ lanekeep init # a config plus a first rule, both runnable
174
- ```
264
+ **Exit codes:** `0` clean, `1` violations found, `2` the checker could not run. A caller has to be
265
+ able to tell "your code has problems" from "the tool is broken". `--warn-only` reports violations
266
+ but exits `0`, for a phased rollout.
175
267
 
176
- To find out where a run spent its time — the split says whether the query or the code is
177
- the problem:
268
+ ## In CI, editors and agents
178
269
 
179
- ```
180
- $ lanekeep check --profile
270
+ ```bash
271
+ lanekeep check --staged # pre-commit
272
+ lanekeep check --format sarif # GitHub code scanning
273
+ lanekeep server # LSP, for any editor
274
+ lanekeep server --protocol mcp # MCP, for an agent host
181
275
  ```
182
276
 
183
- To find out what a rule wants without opening its source:
277
+ MCP exposes three tools `lanekeep_check`, `lanekeep_rules`, `lanekeep_explain` so an agent
278
+ can ask what it broke and what the rule wants without shelling out and parsing text.
184
279
 
185
- ```
186
- $ lanekeep explain lanekeep/no-default-export
187
- $ lanekeep rules --json
188
- ```
280
+ Worked examples for each, including SARIF upload and adopting on an existing codebase, are in
281
+ **[CI and Editors](https://github.com/fmsouza/lanekeep/wiki/CI-and-Editors)**.
189
282
 
190
- For fast feedback on what you touched:
283
+ ## How it stays fast with programmable rules
284
+
285
+ The usual problem with a native tool that runs JavaScript plugins is the boundary between them:
286
+ dispatching into JS once per AST node means tens of thousands of crossings per file.
287
+
288
+ lanekeep dispatches once per **query match** instead. The tree-sitter query runs in Rust across a
289
+ single shared parse; only matches reach your handler. That is typically two to three orders of
290
+ magnitude fewer crossings, and it is the reason a Rust engine still earns its place once rules are
291
+ TypeScript.
191
292
 
192
293
  ```
193
- $ lanekeep check --staged # what is about to be committed
194
- $ lanekeep check --since main # what changed against a ref
294
+ discover paths (globs, gitignore-aware)
295
+ └─> for each file, in parallel:
296
+ cache key ──hit──> validate tracked deps ──> cached violations + facts
297
+ └─miss─> path and raw-text gates reject before any parse
298
+ └─> parse ─> match queries in Rust
299
+ └─> invoke the TypeScript handler, per match only
300
+ └─> reduce phase: cross-file rules consume facts only, never parse trees
301
+ └─> filter suppressions ─> sort ─> report
195
302
  ```
196
303
 
197
- Both are intersected with the config's `include`/`exclude`, and both skip cross-file rules
198
- a whole-corpus rule over a subset gives a wrong answer, not a smaller one, so they are
199
- skipped and named on stderr rather than quietly producing one.
304
+ A warm run with no changes executes no JavaScript at all — every file is a cache hit.
305
+
306
+ Violations are always sorted by `(ruleId, file, line, column)`, and the sandbox withholds the clock
307
+ and randomness, so two runs over identical input produce byte-identical output. An agent reading
308
+ the output twice must not see reordering as change.
309
+
310
+ ## Installing without a package manager
311
+
312
+ Prebuilt for macOS on Apple silicon, Linux on x86-64 and arm64, and Windows on x86-64. The Linux
313
+ binaries are built against **glibc 2.17**, so they run on anything from RHEL 7 onwards.
200
314
 
201
- Exit `0` when clean, `1` when violations are found, `2` when the checker could not run —
202
- a caller has to be able to tell "your code has problems" from "the tool is broken".
203
- Four output formats: `human` (default), `json` (versioned, stable schema), `sarif` (GitHub
204
- code scanning), and `agent` token-minimal, grouped by rule rather than by file, with each
205
- rule's card stated once instead of once per violation. Diagnostics always go to stderr, so
206
- piping into a parser works even when something fails.
315
+ Intel macOS is not prebuilt — `cargo install lanekeep-cli` builds it from source, and both the npm
316
+ launcher and the Homebrew formula say so rather than failing obscurely.
317
+
318
+ **No runtime is required to run lanekeep**, even though rules are written in TypeScript. Node,
319
+ Python or Go is needed only to install it from that ecosystem, where it picks which binary to
320
+ fetch. Nothing is pulled in as a dependency any of those ways.
321
+
322
+ The Go package is a small launcher, because Go can only install and pin things written in Go: it
323
+ fetches the real binary on first use, verifies it against the release's published checksums, and
324
+ caches it. Set `LANEKEEP_BINARY` to an already-installed lanekeep and it fetches nothing.
207
325
 
208
326
  ## Documentation
209
327
 
328
+ **The [wiki](https://github.com/fmsouza/lanekeep/wiki) is the place to start** — it is task-shaped and organized by language.
329
+
330
+ | Page | Purpose |
331
+ | --- | --- |
332
+ | [Getting Started](https://github.com/fmsouza/lanekeep/wiki/Getting-Started) | Install and catch something, in about a minute |
333
+ | [Configuration](https://github.com/fmsouza/lanekeep/wiki/Configuration) | `lanekeep.json`, every field |
334
+ | [Writing Rules](https://github.com/fmsouza/lanekeep/wiki/Writing-Rules) | Rule anatomy and the full host API |
335
+ | [CI and Editors](https://github.com/fmsouza/lanekeep/wiki/CI-and-Editors) | Pre-commit, GitHub Actions, LSP, MCP |
336
+ | [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 |
337
+
338
+ In-repo, versioned with the code:
339
+
210
340
  | Document | Purpose |
211
341
  | --- | --- |
212
342
  | [`docs/architecture.md`](docs/architecture.md) | The full design: execution model, host API, cache, milestones |
213
343
  | [`docs/built-in-rules.md`](docs/built-in-rules.md) | The rules lanekeep ships with, and their options |
214
344
  | [`docs/cross-file-rules.md`](docs/cross-file-rules.md) | Writing a rule that needs a whole-corpus view |
215
- | [`AGENTS.md`](AGENTS.md) | How to work in this repository for coding agents and humans alike |
345
+ | [`docs/adr/`](docs/adr/) | Decision records: why the design is the way it is |
216
346
  | [`CONTRIBUTING.md`](CONTRIBUTING.md) | Setup, commands, and the pull request process |
347
+ | [`AGENTS.md`](AGENTS.md) | How to work in this repository — for coding agents and humans alike |
217
348
  | [`SECURITY.md`](SECURITY.md) | Threat model and how to report a vulnerability |
218
349
  | [`docs/releasing.md`](docs/releasing.md) | How a release is built, gated and published |
350
+ | [`CHANGELOG.md`](CHANGELOG.md) | What changed, per release |
219
351
 
220
352
  ## Security
221
353
 
@@ -228,10 +360,10 @@ Rules are executable code, so the posture is about confinement rather than absen
228
360
  - **No network access.** Ever, in any mode, with no configuration that enables it.
229
361
  - **Filesystem confinement.** Reads go through a tracked `ctx.readFile`, confined to the project
230
362
  root. Writes happen only under `--fix`, only to matched files, only within reported ranges.
231
- - **Bounded execution.** A per-invocation timeout, a 15-second global run budget and a per-runtime
232
- memory ceiling, none disableable — a rule that hangs a pre-commit hook is indistinguishable from
233
- a broken tool. Breaching any of them cancels the run and exits `2`, rather than reporting a
234
- partial result as a clean one.
363
+ - **Bounded execution.** A per-invocation timeout, a global run budget and a per-runtime memory
364
+ ceiling, none disableable — a rule that hangs a pre-commit hook is indistinguishable from a
365
+ broken tool. Breaching any of them cancels the run and exits `2`, rather than reporting a partial
366
+ result as a clean one.
235
367
  - **Deterministic by construction.** The sandbox withholds the clock and randomness, so a rule
236
368
  cannot introduce nondeterminism even by accident.
237
369
 
@@ -239,14 +371,33 @@ This bounds blast radius and makes third-party rule sets reviewable. It is not a
239
371
  someone who can already commit to the repository being checked. To report a vulnerability, see
240
372
  [`SECURITY.md`](SECURITY.md).
241
373
 
374
+ ## Project status
375
+
376
+ **Released and usable.** The current version is on [crates.io](https://crates.io/crates/lanekeep-cli),
377
+ [npm](https://www.npmjs.com/package/lanekeep), [PyPI](https://pypi.org/project/lanekeep/), Homebrew,
378
+ and as a Go module — one build feeding every channel, so the bytes are identical whichever you use.
379
+
380
+ It is **0.x**, and this repository treats that as semver does: a minor bump may break a public Rust
381
+ API. Rule authors are insulated from that — `ctx` methods and the config shape are additive — but
382
+ pin a version if you embed the crates.
383
+
384
+ Known gaps, stated rather than implied:
385
+
386
+ - **No editor types for rule authors yet** (above).
387
+ - **The performance budgets in [`docs/architecture.md`](docs/architecture.md) §15 are not met.**
388
+ They are targets, and that document says by how much and what the levers are. The tool is fast;
389
+ the numbers are simply ambitious.
390
+ - **No type-aware analysis**, by design. Binding resolution is syntactic — see §1 non-goals.
391
+
242
392
  ## Contributing
243
393
 
244
- Contributions are welcome, particularly new built-in rules and new host API surface. Start
245
- with [`CONTRIBUTING.md`](CONTRIBUTING.md) — `./scripts/setup-dev.sh` installs everything and
246
- wires the git hooks.
394
+ Contributions are welcome, particularly new built-in rules and new host API surface. Start with
395
+ [`CONTRIBUTING.md`](CONTRIBUTING.md) — `./scripts/setup-dev.sh` installs everything and wires the
396
+ git hooks, and `just check` is the same gate CI runs.
247
397
 
248
- All work ships as squashed pull requests with [Conventional Commits](https://www.conventionalcommits.org/)
249
- titles. `main` is protected and takes no direct pushes.
398
+ All work ships as squashed pull requests with
399
+ [Conventional Commits](https://www.conventionalcommits.org/) titles. `main` is protected and takes
400
+ no direct pushes.
250
401
 
251
402
  ## License
252
403
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lanekeep",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Deterministic, AST-based architectural conformance checking",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
@@ -20,9 +20,9 @@
20
20
  "node": ">=18"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@lanekeep/darwin-arm64": "0.4.0",
24
- "@lanekeep/linux-arm64": "0.4.0",
25
- "@lanekeep/linux-x64": "0.4.0",
26
- "@lanekeep/win32-x64": "0.4.0"
23
+ "@lanekeep/darwin-arm64": "0.5.0",
24
+ "@lanekeep/linux-arm64": "0.5.0",
25
+ "@lanekeep/linux-x64": "0.5.0",
26
+ "@lanekeep/win32-x64": "0.5.0"
27
27
  }
28
28
  }