lanekeep 0.8.0 → 0.9.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 (3) hide show
  1. package/README.md +19 -14
  2. package/index.d.ts +276 -6
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  [![PyPI](https://img.shields.io/pypi/v/lanekeep?label=pypi)](https://pypi.org/project/lanekeep/)
8
8
  [![CI](https://github.com/fmsouza/lanekeep/actions/workflows/ci.yml/badge.svg)](https://github.com/fmsouza/lanekeep/actions/workflows/ci.yml)
9
9
  [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)
10
+ [![MSRV](https://img.shields.io/badge/MSRV-1.94-blue.svg)](Cargo.toml)
10
11
 
11
12
  lanekeep enforces the conventions that live in your team's heads and your reviewers' comments —
12
13
  the ones a language model cannot infer from the code it is shown. Every rule is a codified answer
@@ -67,20 +68,18 @@ Three things follow from who reads the output:
67
68
  the sandbox withholds the clock and randomness, so two runs over identical input produce
68
69
  byte-identical output. An agent reading it twice must not see reordering as change.
69
70
  - **It runs in the inner loop.** Agents and developers invoke it after every edit, so a warm run
70
- is measured in tens of milliseconds for a config whose rules are all TypeScript modules. A
71
- rule that ships as a compiled component has to be loaded first, and the four TypeScript
72
- built-ins share a 12.4 MiB one: a config naming all four of them costs **about 6.5 seconds on a
73
- project's first run** and **about 0.2 seconds on every run after it** — the component is
74
- deserialized once per run, not once per rule — and leaves 33 MiB in `.lanekeep`.
75
- `lanekeep init` scaffolds one of those four, so that is what a new TypeScript project meets
76
- first. [`docs/architecture.md`](docs/architecture.md) §15 has the table and what is owed.
71
+ is measured in tens of milliseconds. The built-ins that ship as WebAssembly components are all
72
+ under 115 KB, so loading them is noise a 12.4 MiB compiled-TypeScript component that once
73
+ cost new TypeScript projects ~6.5 seconds on their first run was reverted for exactly that
74
+ reason.
75
+ [`docs/architecture.md`](docs/architecture.md) §15 has the ledger.
77
76
 
78
77
  **Rules are authored in TypeScript whatever language they check** — that is the form to start
79
78
  from, and it is the one most teams already have someone who writes. A rule may also be a
80
- WebAssembly component, which is how eight of the ten built-ins ship — two written in Rust, two
81
- written in Go, and four compiled ahead of time from the same TypeScript they were already
82
- written in. Every form reaches the same host API and is held to the same limits, and a config
83
- names a rule rather than its implementation. **Configuration is neither** — `lanekeep.json` is
79
+ WebAssembly component, which is how four of the sixteen built-ins ship — two written in Rust
80
+ and two written in Go; the other twelve run as QuickJS modules, three of them checking five of
81
+ the six supported languages from a single source (every one but JavaScript). Every form reaches the same host API and is held to
82
+ the same limits, and a config names a rule rather than its implementation. **Configuration is neither** — `lanekeep.json` is
84
83
  plain data, so a Go, Python or Rust team never writes a `.ts` file except when authoring an
85
84
  actual rule.
86
85
 
@@ -92,7 +91,7 @@ lanekeep check --staged # only what is about to be committed
92
91
  lanekeep check --since main # only what changed against a ref
93
92
  lanekeep check --watch # re-check on every change, until Ctrl-C
94
93
  lanekeep check --fix # apply the safe fixes, report what is left
95
- lanekeep check --profile # where the run spent its time, per rule
94
+ lanekeep check --profile # per rule: where the time went, and what it looked at
96
95
  lanekeep rules # what this project has configured
97
96
  lanekeep explain <rule-id> # one rule's card, without opening its source
98
97
  lanekeep server # LSP for an editor, or --protocol mcp for an agent host
@@ -183,7 +182,9 @@ Known gaps, stated rather than implied:
183
182
  - **Two of the three performance budgets in [`docs/architecture.md`](docs/architecture.md)
184
183
  §15 are not met.** The cold budget is; they are targets, and that section says by how much
185
184
  and where the remaining time goes.
186
- - **No type-aware analysis**, by design. Name resolution is syntactic see §1 non-goals.
185
+ - **No general type inference**, by design. Name resolution is syntactic; a rule that opts in
186
+ (`requires: ['types']`) gets a bounded within-file oracle that answers `undefined` rather than
187
+ guess — see §1 non-goals and §6.10.
187
188
 
188
189
  ## Documentation
189
190
 
@@ -204,7 +205,11 @@ In-repo, versioned with the code:
204
205
  | [`docs/architecture.md`](docs/architecture.md) | The full design: execution model, host API, cache, milestones |
205
206
  | [`docs/built-in-rules.md`](docs/built-in-rules.md) | The rules lanekeep ships with, and their options |
206
207
  | [`docs/cross-file-rules.md`](docs/cross-file-rules.md) | Writing a rule that needs a whole-corpus view |
207
- | [`docs/adr/`](docs/adr/) | Decision records: why the design is the way it is |
208
+ | [`docs/obligation-rules.md`](docs/obligation-rules.md) | Writing a rule that needs a resource released on every path |
209
+ | [`docs/type-aware-rules.md`](docs/type-aware-rules.md) | Writing a rule that needs to know what a value's type is |
210
+ | [`docs/authoring-rust-rules.md`](docs/authoring-rust-rules.md) | Writing a rule in Rust, shipped as a WebAssembly component |
211
+ | [`docs/authoring-go-rules.md`](docs/authoring-go-rules.md) | Writing a rule in Go |
212
+ | [`docs/authoring-python-rules.md`](docs/authoring-python-rules.md) | Writing a rule in Python |
208
213
  | [`CONTRIBUTING.md`](CONTRIBUTING.md) | Setup, commands, and the pull request process |
209
214
  | [`AGENTS.md`](AGENTS.md) | How to work in this repository — for coding agents and humans alike |
210
215
  | [`SECURITY.md`](SECURITY.md) | Threat model and how to report a vulnerability |
package/index.d.ts CHANGED
@@ -8,11 +8,14 @@
8
8
  * Node: `defineRule` and `defineConfig` are identity functions whose only job is to give the
9
9
  * compiler something to check against, and `RuleContext` is provided by lanekeep at run time.
10
10
  * The world is the single source of truth for every member the renderer emits straight from it.
11
- * Two members deviate from the world on purpose, and both are QuickJS-shaped: `today` is omitted
12
- * from `RuleContext` because QuickJS exposes it as a conditional property rather than a callable,
13
- * a shape this renderer cannot state honestly from the world; and `facts` is added to
11
+ * Three members deviate from the world on purpose, and all three are QuickJS-shaped: `today` is
12
+ * omitted from `RuleContext` because QuickJS exposes it as a conditional property rather than a
13
+ * callable, a shape this renderer cannot state honestly from the world; `facts` is added to
14
14
  * `RuleContext` because QuickJS hands a per-file rule `facts` that the world declares only on
15
- * `reduce-context`. Nothing else is added or omitted by hand.
15
+ * `reduce-context`; and `types` is added to `RuleContext` because `ctx.types` — the bounded
16
+ * type oracle — is QuickJS-only and has no presence in `world.wit` at all: a component rule
17
+ * cannot declare `requires`, so there is nothing for the world to say about it. Nothing else is
18
+ * added or omitted by hand.
16
19
  */
17
20
 
18
21
  /**
@@ -78,7 +81,23 @@ export interface RuleCard {
78
81
  }
79
82
  }
80
83
 
81
- /** Cheap rejections applied before a file is read or parsed. */
84
+ /**
85
+ * Cheap rejections applied before a file is read or parsed.
86
+ *
87
+ * A gate is declared, not derived: nothing here is computed from the rule's `query`, and
88
+ * nothing checks the two against each other. So a gate can change what the rule reports — a
89
+ * file it rejects is a file the rule never runs on, and a violation there is never found.
90
+ *
91
+ * A gate is neutral when it admits every file the rule would have reported on — yours to
92
+ * keep, and not something the engine can check. The safe way to keep it is to gate wider
93
+ * than the query, which is sufficient rather than necessary: a rule whose handler filters
94
+ * may gate far narrower and still be neutral. `--profile` prints, per rule, how many files
95
+ * each gate rejected and how many the rule actually parsed, which is where a suspected gate
96
+ * is settled. A nonzero `cached` means the columns to its right are
97
+ * incomplete for that run, since a cache hit returns before the content gates are consulted;
98
+ * pair `--profile` with `--no-cache` to read them. `path-gated` is unaffected, because a path
99
+ * gate runs before the cache is consulted at all.
100
+ */
82
101
  export interface Gates {
83
102
  /**
84
103
  * Glob patterns a file's path must match for the rule to consider it.
@@ -191,6 +210,151 @@ export interface StructureFingerprint {
191
210
  nodes: number
192
211
  }
193
212
 
213
+ /**
214
+ * Where a name came from. Returned by {@link TypeApi.symbolOf} directly, and nested under a
215
+ * {@link TypeInfo} whose `symbol` field is set.
216
+ *
217
+ * There is deliberately no `file` naming the declaring path, even now that declarations are
218
+ * read across files. It would be where a package happens to be installed —
219
+ * `node_modules/@types/money/index.d.ts` on one machine and a workspace path on another —
220
+ * so a rule branching on it would give different answers for one program. `module` is the
221
+ * stable identity, and `exported` is the name that module declares it under.
222
+ */
223
+ export interface SymbolInfo {
224
+ /**
225
+ * The name as it appears at the use site, not at the declaration. For a renamed import —
226
+ * `import { Decimal as Money }` — this is the local alias `Money`. It is the spelling to
227
+ * quote in a message, because it is the spelling the reader has in front of them; compare
228
+ * `exported` when the question is which export of a module this is.
229
+ */
230
+ name: string
231
+ /**
232
+ * The name the module exports this under. For a named import — `import { Decimal }` and
233
+ * `import { Decimal as Money }` alike — this is `Decimal` when `m`'s declaration file is
234
+ * unreadable, and the name that file actually declares (following any re-export chain)
235
+ * when it is readable, which need not be `Decimal` at all. Copied even when nothing was
236
+ * renamed, so a comparison never needs a fallback to `name` that would silently accept
237
+ * every plain import if it were forgotten. For a default import this is the name the
238
+ * declaration file declares the default export under when that file is readable — `Big`
239
+ * for a package whose default export is `Big`, whatever the local binding is called — and
240
+ * falls back to the placeholder `default` only when the declaration is not readable.
241
+ * Absent for a namespace import, which binds the module object and has no single exported
242
+ * name, and absent for a local declaration, which was not imported at all.
243
+ */
244
+ exported?: string
245
+ /**
246
+ * The module it was imported from. Absent for a local declaration — that absence is what
247
+ * distinguishes an imported `Decimal` from a local class that happens to share the name.
248
+ */
249
+ module?: string
250
+ }
251
+
252
+ /**
253
+ * What the oracle established about an expression, from {@link TypeApi.typeOf}.
254
+ *
255
+ * At most one of `primitive`, `symbol` or `union` is set, matching which kind of type this
256
+ * is — a `union`'s members are already flattened one level and in canonical order. `text` is
257
+ * set alongside whichever it is, but it is **display-only**: what TypeScript itself would
258
+ * call the type, for a message a rule builds. Branch on `primitive` and `symbol`, never on
259
+ * `text`'s wording.
260
+ *
261
+ * All three can be unset at once: a *nominal* type whose name the resolver could not
262
+ * attribute — an unresolvable, global or ambient type such as `Date` used with no local
263
+ * declaration or import — carries only `text`. That is not a gap to code around; it is
264
+ * another shape of the same "I could not be sure" answer this whole surface is built on,
265
+ * the same posture `typeOf` itself takes by returning `undefined` rather than guessing. Do
266
+ * not assume the final branch of `if (primitive) … else if (symbol) … else` is unreachable
267
+ * — for this shape, it is not.
268
+ *
269
+ * There is deliberately no `complete` field on a *type*. Whether the oracle had a full view
270
+ * is a property of the file rather than of any one answer — `TypeApi.complete()` is where it
271
+ * is asked — and putting it here would invite a rule to read it per type and conclude
272
+ * something different on each.
273
+ */
274
+ export interface TypeInfo {
275
+ /** What TypeScript would call this type. Display-only — branch on the fields below instead. */
276
+ text: string
277
+ /** Set when this is a primitive — exactly the set TypeScript itself recognizes as one. */
278
+ primitive?: 'number' | 'string' | 'boolean' | 'bigint' | 'symbol' | 'null' | 'undefined'
279
+ /**
280
+ * Set when this is a named type and the oracle could resolve where the name came from.
281
+ * Absent on an unresolvable, global or ambient nominal type — see the interface doc above.
282
+ */
283
+ symbol?: SymbolInfo
284
+ /** Set when this is a union. */
285
+ union?: TypeInfo[]
286
+ }
287
+
288
+ /**
289
+ * The type oracle, reached through `ctx.types`.
290
+ *
291
+ * Every question can come back with no answer, and no answer is a first-class result rather
292
+ * than a failure to work around: the oracle is conservative on purpose, and it would rather
293
+ * say nothing than say something wrong, because a rule reporting on a wrong type accuses
294
+ * correct code. A rule is expected to check for `undefined` and quietly stay silent, the same
295
+ * posture the rest of the navigation surface already takes on a dead handle.
296
+ *
297
+ * It reads the file in front of it **and the declaration files that file imports**, through
298
+ * the same tracked, confined reads `ctx.readFile` uses. Nothing above the project root is read,
299
+ * ever — so point lanekeep at the workspace root, the directory `node_modules` lives in,
300
+ * rather than at a package inside it. `--config` does not move the root.
301
+ */
302
+ export interface TypeApi {
303
+ /**
304
+ * The type of the expression at `n`. `undefined` is that first-class no-answer, not a
305
+ * failure.
306
+ */
307
+ typeOf(n: Node): TypeInfo | undefined
308
+ /** Where the identifier at `n` was declared. `undefined` on the same terms as `typeOf`. */
309
+ symbolOf(n: Node): SymbolInfo | undefined
310
+ /**
311
+ * What calling the function at `n` yields.
312
+ *
313
+ * Separate from `typeOf` because a function declaration is not an expression. Accepts a
314
+ * call expression, a function-like declaration, or an identifier bound to one. An
315
+ * annotated signature answers its annotation; an unannotated one with a single `return`
316
+ * answers that expression's type; several returns answer their union when every member is
317
+ * known and `undefined` otherwise; a function with no `return` answers `undefined` rather
318
+ * than guessing `void`. Type arguments are dropped throughout — `useQuery<Balance[]>`
319
+ * answers by the result type's name, never by the argument.
320
+ */
321
+ returnTypeOf(n: Node): TypeInfo | undefined
322
+ /**
323
+ * Whether the type at `n` is the type `module` exports as `name`, or inherits from it.
324
+ *
325
+ * Nominal, never structural: a class reaches it through `extends` or `implements`, an
326
+ * interface through `extends`, an alias by being transparent, and a union only when *every*
327
+ * member does. A primitive answers `false` — but only when `module` and `name` resolve to a
328
+ * declaration; a target that does not resolve answers `undefined` whatever `n` is.
329
+ *
330
+ * **`undefined` is not `false`.** `false` means the walk completed and reached nothing;
331
+ * `undefined` means a link could not be read — an unresolvable import, a package that is
332
+ * not installed, a type with no symbol at all. A rule treating the two alike reports on
333
+ * code the oracle never saw.
334
+ */
335
+ isAssignableTo(n: Node, module: string, name: string): boolean | undefined
336
+ /**
337
+ * Whether every import in the file being checked resolved to something readable.
338
+ *
339
+ * `false` is the honest label on a partial view: some name in this file came from a module
340
+ * the oracle could not open, so an `undefined` anywhere in it may be ignorance rather than
341
+ * a considered answer. A rule that reports only on what it established does not need to
342
+ * ask; a rule that wants to say "I could not check this file" does.
343
+ *
344
+ * An import that resolves to a file which does not *parse* counts as unreadable too: the
345
+ * names outside the broken span still answer, the ones inside it come back `undefined`, and
346
+ * nothing on either answer says which. An import of something that is not code — a
347
+ * stylesheet, a JSON asset, an image — is not counted at all, since it is not a module the
348
+ * oracle reads.
349
+ *
350
+ * **The verdict is the whole file's, and a parse fault is the whole declaration file's.**
351
+ * One `ERROR` node anywhere in a fifty-thousand-line `@types` bundle makes every file that
352
+ * imports it `false`, however far that span is from the names the rule asked about. Silence
353
+ * is the safe direction; a narrower verdict is a refinement rather than a promise.
354
+ */
355
+ complete(): boolean
356
+ }
357
+
194
358
  /** A rule's RuleContext surface. */
195
359
  export interface RuleContext {
196
360
  readonly filePath: string
@@ -219,6 +383,19 @@ export interface RuleContext {
219
383
  report(at: Node, message?: string | ReportOptions): void
220
384
  /** Facts emitted so far, optionally filtered by `kind`. */
221
385
  facts(kind?: string): EmittedFact[]
386
+ /**
387
+ * The type oracle, present only for a rule that declared `requires: ['types']`.
388
+ * Bounded by a fixed depth rather than by file: it follows an import into the
389
+ * declaration file that answers it, and the file after that.
390
+ *
391
+ * Typed as always present because there is no way to spell "present when
392
+ * this rule's own `requires` says so" as a type — so a rule that forgets the
393
+ * declaration still compiles. It finds out at the first call instead:
394
+ * `ctx.types` is `undefined` at run time, and `ctx.types.typeOf(...)` throws a
395
+ * `TypeError` rather than returning a quietly wrong answer. That loudness is
396
+ * deliberate.
397
+ */
398
+ types: TypeApi
222
399
  }
223
400
 
224
401
  /** A violation the reduce phase reports, which has no node to point at. */
@@ -238,6 +415,49 @@ export interface ReduceContext {
238
415
  report(at: ReduceLocation, message?: string | ReportOptions): void
239
416
  }
240
417
 
418
+ /** Queries whose named captures drive the taint analysis. */
419
+ export type FlowSpec = {
420
+ /** Queries whose `@source` capture marks a tainted origin. */
421
+ sources: string[]
422
+ /** Queries whose `@sink` capture marks a forbidden destination. */
423
+ sinks: string[]
424
+ /** Queries whose `@sanitizer` capture clears taint from the value it produces. */
425
+ sanitizers?: string[]
426
+ }
427
+
428
+ /** One tainted flow: a source whose value reaches a sink with no intervening sanitizer. */
429
+ export type FlowPath = {
430
+ readonly source: Node
431
+ readonly sink: Node
432
+ /** The assignments and calls between source and sink, in flow order. One canonical path. */
433
+ readonly steps: readonly Node[]
434
+ }
435
+
436
+ /**
437
+ * A typestate obligation: an acquired resource must reach a release on every
438
+ * path out of `scope`. Requires `requires: ['dataflow']`.
439
+ */
440
+ export type ObligationSpec = {
441
+ /** Queries whose `@acquire` capture starts an obligation on the captured value. */
442
+ acquire: string[]
443
+ /** Queries whose `@release` capture discharges it. */
444
+ release: string[]
445
+ /**
446
+ * `'function'` — every path out of the enclosing function, `return`/`throw` included.
447
+ * `'block'` — every path out of the block the acquire is in.
448
+ */
449
+ scope: 'function' | 'block'
450
+ }
451
+
452
+ /** An acquire some path leaves undischarged, passed to `checkObligation`. */
453
+ export type UnmetObligation = {
454
+ readonly acquire: Node
455
+ /** The exit the value escapes through — a `return`, a `throw`, or the implicit end. */
456
+ readonly exit: Node
457
+ /** Whether any path *did* discharge it: partial coverage reads differently to none. */
458
+ readonly partial: boolean
459
+ }
460
+
241
461
  /** A rule, as `defineRule` takes it. */
242
462
  export interface Rule {
243
463
  /**
@@ -256,6 +476,15 @@ export interface Rule {
256
476
  * or Rust rule means it silently never fires.
257
477
  */
258
478
  language?: LanguageId | LanguageId[]
479
+ /**
480
+ * Host analyses this rule needs before it can run.
481
+ *
482
+ * Absent means none, which is every rule today. A rule declaring one the engine cannot
483
+ * provide is refused at load rather than run without it: an analysis that silently goes
484
+ * missing makes the rule report nothing, and a rule reporting nothing is indistinguishable
485
+ * from a codebase with nothing to report.
486
+ */
487
+ requires?: Array<'types' | 'dataflow'>
259
488
  /** How serious a violation is, before any config override. */
260
489
  severity: Severity
261
490
  /** What the rule tells whoever has to act on it. */
@@ -269,6 +498,11 @@ export interface Rule {
269
498
  * what keeps a JavaScript rule affordable. Write the narrowest query that captures what
270
499
  * you need; `check` then only refines.
271
500
  *
501
+ * Optional — required only when `check` is present. A flow-only rule (`checkFlow` with no
502
+ * `check`) declares no top-level query; its file gate comes from the union of its `flow`
503
+ * queries instead (see {@link FlowSpec}). The type cannot express that conditional, so a
504
+ * `check` rule missing `query` type-checks here and is refused by the config loader instead.
505
+ *
272
506
  * A single string applies to every declared language. An object maps each declared
273
507
  * language to its own query — required when the grammars do not share node vocabulary
274
508
  * (Python spells a call `call`, the other supported grammars say `call_expression`).
@@ -282,13 +516,29 @@ export interface Rule {
282
516
  * no backreferences or lookaround. `#is?`, `#is-not?`, `#set!`, or an operator the
283
517
  * binding does not know is refused at compile time.
284
518
  */
285
- query: string | Partial<Record<LanguageId, string>>
519
+ query?: string | Partial<Record<LanguageId, string>>
286
520
  /** A per-invocation budget overriding the default, in milliseconds. */
287
521
  timeout?: number
288
522
  /** Called once per query match. */
289
523
  check?(ctx: RuleContext, match: Match): void
290
524
  /** Called once per run, after every file, with facts only. */
291
525
  reduce?(ctx: ReduceContext): void
526
+ /**
527
+ * A taint-flow specification: queries whose captures mark tainted origins, forbidden
528
+ * destinations, and the calls that neutralize a value. Requires `requires: ['dataflow']`
529
+ * and a `checkFlow` handler; a rule declaring one without the other is refused at load.
530
+ */
531
+ flow?: FlowSpec
532
+ /** Called once per canonical tainted flow from a source to a sink. */
533
+ checkFlow?(ctx: RuleContext, path: FlowPath): void
534
+ /**
535
+ * A typestate obligation: queries whose captures acquire an obligation on a value and
536
+ * queries that discharge it. Requires `requires: ['dataflow']` and a `checkObligation`
537
+ * handler; a rule declaring one without the other is refused at load.
538
+ */
539
+ obligation?: ObligationSpec
540
+ /** Called once per value left with an unmet obligation at the end of its scope. */
541
+ checkObligation?(ctx: RuleContext, unmet: UnmetObligation): void
292
542
  }
293
543
 
294
544
  /** A lanekeep configuration, as `defineConfig` takes it. */
@@ -307,6 +557,13 @@ export interface Config {
307
557
  rule?: number
308
558
  /** Wall-clock, for the whole run. */
309
559
  global?: number
560
+ /**
561
+ * Host-side type-provider work, across the whole run.
562
+ *
563
+ * Analysis time rather than elapsed time: only what the provider spends building
564
+ * programs and answering requests is charged against it.
565
+ */
566
+ analysis?: number
310
567
  }
311
568
  /** Policy for suppression directives. All off by default. */
312
569
  suppressions?: {
@@ -317,6 +574,19 @@ export interface Config {
317
574
  /** Any whole-file directive is reported. */
318
575
  forbidFileScope?: boolean
319
576
  }
577
+ /** Which type oracle answers `ctx.types`. */
578
+ types?: {
579
+ /**
580
+ * `builtin` is lanekeep's own bounded oracle and needs no toolchain. `tsc` drives the
581
+ * project's own `typescript` package through a sidecar process: wider answers, a slower
582
+ * run, and a pre-commit hook that builds the project's program before it checks anything.
583
+ */
584
+ provider?: 'builtin' | 'tsc'
585
+ /** How to launch the sidecar. `tsc` only. */
586
+ command?: string[]
587
+ /** The `typescript` package the sidecar loads, resolved from the project root. `tsc` only. */
588
+ typescript?: string
589
+ }
320
590
  /** The rules to run, in order. */
321
591
  rules: Rule[]
322
592
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lanekeep",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Deterministic, AST-based architectural conformance checking",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
@@ -23,10 +23,10 @@
23
23
  "node": ">=18"
24
24
  },
25
25
  "optionalDependencies": {
26
- "@lanekeep/darwin-arm64": "0.8.0",
27
- "@lanekeep/linux-arm64": "0.8.0",
28
- "@lanekeep/linux-x64": "0.8.0",
29
- "@lanekeep/win32-x64": "0.8.0"
26
+ "@lanekeep/darwin-arm64": "0.9.0",
27
+ "@lanekeep/linux-arm64": "0.9.0",
28
+ "@lanekeep/linux-x64": "0.9.0",
29
+ "@lanekeep/win32-x64": "0.9.0"
30
30
  },
31
31
  "main": "index.js",
32
32
  "types": "index.d.ts",