@elaraai/east-diagnostics

中文文档
1.0.107发布于 2026/11/13AGPL-3.0-or-laterTypeScriptES Module零依赖构建来源已验证近 30 天下载 3723

East-aware diagnostic rules over the TypeScript checker — one shared engine for the editor (tsserver plugin), the agent (headless daemon), and CI.

原始使用文档
文档目录9 节
  1. Features
  2. Rules
  3. East-side idiom hygiene
  4. Host-vs-East
  5. Deploy/runtime-failure classes that type-check clean
  6. Host code holding decoded East values
  7. Usage
  8. Claude Code plugin
  9. License

East Diagnostics

East-aware diagnostic rules over the TypeScript checker

License Node Version

East Diagnostics is the shared rule engine that catches East-specific mistakes which plain TypeScript can't — the same checks surfaced to the agent at write-time (the Claude plugin daemon) and to developers in the editor and CI (@elaraai/eslint-plugin-east). The rules run against a real ts.Program, so they are type-aware, not regex heuristics.

Every rule self-gates on East-ness — it fires only where there is genuine East code (an East type/block, an e3 construct, or an @elaraai/* import), so the set is opt-in by installation and inert on plain TypeScript. There is no package-identity allow/deny list; per-project suppression is the disabled option, like any linter.

Features

  • Shared rule set - One engine, runEastRules(ts, program, sourceFile, checker), reused across every surface.
  • Type-aware - Rules consult the TypeScript checker (e.g. resolving BlockBuilder, variant contextual types).
  • No bundled compiler - typescript is a peer dependency; the host's version is injected.
  • Diagnostics service - createDiagnosticsService() resolves the nearest tsconfig, holds a warm LanguageService, and merges native type errors with the East rules for a file. Supports in-memory overlays for unsaved buffers.
  • Readable East type errors - native TS assignability errors on East types are rewritten via east's structural type diff (diffTypes), so a mismatch deep inside a recursive type reads as one localized line instead of pages of restated generics. The project's own @elaraai/east is resolved at runtime, so the diff always matches the project's type semantics.
  • LSP server - runEastLsp() serves the same diagnostics over the Language Server Protocol (stdio, zero dependencies), usable by Claude Code plugin lspServers, Neovim, or any LSP client.
  • tsserver plugin - the @elaraai/east-diagnostics/tsserver-plugin entry decorates the editor's existing TypeScript language service (no second program), shipped to VS Code via the East UI Preview extension's typescriptServerPlugins contribution.

Rules

East-side idiom hygiene

  • no-redundant-east-cast - Redundant TS type info on the value of $.let/$.const that the East type argument already governs — a cast (as …/<…>), new Map<K,V>() generics, or an East.value(x, T) wrapper.
  • prefer-explicit-east-type - One-arg $.let/$.const on an under-determined value ([], {}, new Map()).
  • prefer-some-none - variant("some"/"none", …) instead of some() / none.
  • no-handrolled-variant - A plain object literal where an East variant/option is expected — or, in a file that imports East, a { type, value } literal with a string tag in any slot but a published API's own type (a declaration file's, like East's Resolution). A literal lacks the brand East tells a variant by.
  • no-east-namespaced-type - East.IntegerType etc. instead of a bare import.
  • prefer-let-const-over-east-value - East.value(…) declared or returned inside an East.function block.
  • no-relative-src-import - Importing another package's internals via ../src/… or a deep @elaraai/x/src path instead of its published name. (A package importing its own src relatively — e.g. a spec's ../src/index.js — is exempt; it cannot import its own published name.)
  • no-let-const-in-expression - $.let/$.const used anywhere other than a const/let initializer, a bare statement, a return, or a concise arrow body (e.g. a struct-field value, array element, call argument, or chain target buries the declaration in an expression).
  • no-unexecuted-east-expression - A bare East expression statement that is never executed with $( … ) or bound. A declaration binds it, and so does an assignment (keyFn = East.function(…), a choice between two functions).
  • no-reinlined-east-binding - An East Expr bound to a JS const/let and read twice inside one East body, or read inside a per-element callback, a $.for/$.while body or a nested East.function, is re-inlined — and re-evaluated — at each read; bind it once with $.let/$.const. One read per exclusive arm (.ifElse, $.if … .else, a match arm) and a $.const read anywhere stay silent, as does a function made outside every East body (East.function, or one a helper or a conditional gives there): it captures no binding, so making it again at each read re-evaluates nothing.
  • prefer-jsx-over-factory-call - In a .tsx file, a factory's Foo.Root(...) whose result is a JSX element — author it with the <Foo> tag instead.
  • no-untracked-east-data - A bare JS const literal consumed in an East-typed position inside a block — bind it with $.const/$.let.

Host-vs-East

These share one principle: inside an East block the code must be East all the way down, and the host language may declare an East/e3 program but never compute its data-dependent IR, keys, or values.

  • no-host-in-east-block - Inside an East block, any host-language construct: a host call (a local TS helper, a JS builtin, a JS Array method), a TS closure/function declaration, a host operator on East operands (a + b, cond ? a : b, &&/||), host index access (arr[i] on a JS value), a JS for/while/if that emits IR, or host string interpolation. (East method chains, $.*, East.*/Expr.*, variant/some/none, East.str, and any @elaraai/* call are East and never flagged.)
  • no-module-scope-east-macro - A module-scope TS helper whose every return builds East IR (an Expr chain, East.value, variant/some over an Expr), or a composite string key ((o, l) => ${o}|${l}``) that feeds East data — make it a real East.function, or model the data with typed keys / nested East structures. A helper building decoded values over host data (a renderer's handler, a test's fixture) and a host storage key are host code, and pass. So do a declaration factory — a helper returning an e3 definition (TaskDef, RecordIndexDef, MigrationDef, …) or a record's part as its builder drafts it (index.on(…), mutation.patch(), migration.value(…)), which composes the program's structure — a return handing back the helper's own parameter, such as a checked narrowing (return fn as FunctionExpr<I, O>), which builds nothing; a program builder, returning its program's IR bundle (toIR()), which serializes it whole; and a function factory, every return a function value made from host data (types, declarations) and other functions, which a program binds and calls. A helper taking an East value other than a function is still checked: a function it returns would capture it.
  • no-compile-time-data-injection - Build-time data ingestion (a node:fs import/call, JSON.parse, process.env) at module scope — load data at runtime via e3.input / datasets / platform tasks. (A genuine seed-time bootstrap with no East-side reader is an accepted warning.)
  • no-compile-time-seed-data - Host-computed data passed as the seed (3rd arg) of e3.input(name, type, variant('value', seed)) — a new Map()/array filled in place by host for-loops, or an object literal of host calls (num(cfg.x), BigInt(...)) — bakes a build-time snapshot into the deployed program. The default must be a small authored constant (a literal, an empty/literal Map/Set/array/struct, or an East value variant/some/none/East.value) or omitted; load real/bulk data at runtime (a BlobType input parsed with blob.decodeCsv(…) in an e3.task, a platform FileSystem.readFile, or an e3.record with its mutations).

Deploy/runtime-failure classes that type-check clean

  • require-runner-platforms - An e3.task calling PROJECT-declared platform functions (East.platform("proj.x", …) stubs) whose options declare no runner.platforms { custom: … } entry — fails only at dataflow runtime with "Platform function … is not available".
  • no-cross-block-builder - A nested East callback emitting bindings via an OUTER block's $ (xs.map((_$, x) => { $.let(…) })) — the binding lands in the wrong block.
  • no-state-outside-reactive - east-ui State.* outside a <Reactive> builder — the UI function becomes async at analysis time and is rejected at deploy.
  • prefer-const-ui-callbacks - An East.function handler written inline in a JSX prop — rebuilt each render, and equalFor can't distinguish function values, so memoized renderers miss the swap. Bind once with $.const.
  • no-dynamic-bind-path - A Data.bind / State.bind key computed from an East value — bind keys must be IR-build constants or the binding is missing from the ui() manifest.
  • no-build-time-clock - Date.now() / argless new Date() at module scope of East/e3 source — the BUILD clock gets baked into the deployed program.
  • no-handrolled-value-type-mirror - A hand-authored interface Foo {…} mirroring an in-scope East type value FooType, an as { type: …; value: … } assertion, or a *Like interface or type literal over East types — derive it with ValueTypeOf<typeof FooType> so it can't drift, and narrow a decoded union on its tag. A factory's options interface (SubtypeExprOrValue<…> members) is an input, not a mirror, and passes.
  • no-host-comparison-on-east-values - ===/< on decoded East values, and a sort comparator that subtracts them — use equalFor(T) / compareFor(T), or Object.is(a, b) where you mean the same object (a memo, a cache).
  • require-example-returns - An example() without returns (for a non-NullType/UIComponentType output) — the harness runs the fn as a bare statement and the assertion false-passes.
  • no-duplicate-definition-name - Two same-kind e3 definitions sharing a name string in one file — they collide at deploy time.
  • no-inline-credentials - A literal string credential (password, secretAccessKey, token, …, directly or wrapped in some(…)/variant("some", …)) in East/e3 source — the IR is content-addressed, exported, and replicated, so a secret in it is effectively unredactable. Use Env.get("NAME") (east-node-std) and supply the value per environment. Credentials paired with a localhost host/endpoint (test containers) are exempt.

Host code holding decoded East values

A renderer, a runtime or a test holds East values once East has decoded them, and each rule here flags a JavaScript stand-in for one of East's utilities. Each stand-in type-checks clean, and several hid real bugs in the renderers (#960, #962). A value is recognised by where its type comes from — a ValueTypeOf struct, a member carrying [variant_symbol], a SortedSet / SortedMap, or a field, payload or element read out of one — never by a name list. hostValueRuleNames names the set (with no-host-comparison-on-east-values, no-handrolled-value-type-mirror, no-handrolled-variant, prefer-some-none and no-struct-type-from-object), for a caller running only these with only.

  • no-host-print-of-east-values - String(x), x.toString(), `${x}`, JSON.stringify(x), x.toISOString() on a value JavaScript prints differently from East — a Float, a DateTime, a Blob, a struct, a variant, a collection. Use printFor(T), or the locale formatters for a person. A String, an Integer and a Boolean print the same either way, and a number written as a CSS length (`${w}px`) is style, not a print.
  • no-host-parse-to-east-values - BigInt(s), Number(s), parseFloat(s), parseInt(s), new Date(s), Date.parse(s) or JSON.parse(s) reading text into an East slot — an East function value's argument, a variant's payload, a struct's field. Use parseFor(T) and act on its success. Text written in the source is its author's, save a zoneless date-time, which new Date reads in local time.
  • no-js-type-dispatch-on-east-values - typeof x or x instanceof C on an East value — dispatch on its East type (isValueOf(x, T)) or its variant tag.
  • no-js-collection-for-east-collection - A JavaScript Set / Map keyed by structs, variants or DateTimes landing in an East Set, Dict or variant payload — it finds a key by identity, so an equal key never finds its entry. Use SortedSet / SortedMap with compareFor(K). String, Integer, Float and Boolean keys are East's own idiom and pass.
  • no-struct-type-from-object - East's StructType given an object whose fields are known only at run time — Object.fromEntries(…), a Record<string, …> or a generic struct's fields, a spread of one, a computed name of type string — or an object naming a field like an integer ("10", 2024), written out or in its type. An object lists such a name before every other, wherever it was written, so it cannot keep that field in its place, and StructType refuses one (#1574). Declare the fields as a list, StructType([[name, type], …]), which keeps any order; Object.entries(T.fields) lists a struct type's own. StructType(Object.fromEntries(list)) is fixed to StructType(list). An object whose names are known and none like an integer — written out, a const record of them, a declared struct's fields or a spread of them, a literal key — passes.

Usage

import * as ts from "typescript";
import { runEastRules, createDiagnosticsService, hostValueRuleNames } from "@elaraai/east-diagnostics";

// Pure: run the rules over one source file you already have a program + checker for.
const diagnostics = runEastRules(ts, program, sourceFile, checker, { disabled: ["prefer-some-none"] });

// Only the rules over host code holding decoded East values.
const hostValues = runEastRules(ts, program, sourceFile, checker, { only: hostValueRuleNames });

// Or let the service resolve the project and merge native + rule diagnostics.
const service = createDiagnosticsService();
const text = service.diagnoseText("/path/to/file.ts"); // "" when clean

Claude Code plugin

The East ecosystem also ships a Claude Code plugin — East language skills, example search, and preemptive diagnostics for East code — installed separately from the elaraai marketplace:

# Inside Claude Code
/plugin marketplace add elaraai/east-workspace
/plugin install east@elaraai
# From a terminal
claude plugin marketplace add elaraai/east-workspace
claude plugin install east@elaraai

License

Dual-licensed:


Developed by Elara AI Pty Ltd

版本与发布记录

@elaraai/east-diagnostics 共发布 104 个版本,首次发布于 2026/06/09;过去 12 个月发布了 104 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.0.107latest2026/11/13今天
1.0.1062026/11/094 天前
1.0.1052026/11/085 天前
1.0.1042026/11/067 天前
1.0.1032026/11/049 天前
1.0.1022026/11/0310 天前

关于 @elaraai/east-diagnostics 的常见问题

@elaraai/east-diagnostics 是什么?

East-aware diagnostic rules over the TypeScript checker — one shared engine for the editor (tsserver plugin), the agent (headless daemon), and CI.

如何安装 @elaraai/east-diagnostics?

在项目目录运行 npm install @elaraai/east-diagnostics,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:typescript(>=5.0.0 <7)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@elaraai/east-diagnostics 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@elaraai/east-diagnostics 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@elaraai/east-diagnostics 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@elaraai/east-diagnostics 可以商用吗?

@elaraai/east-diagnostics 使用 AGPL-3.0-or-later 许可。最严格的 Copyleft:除分发外,通过网络向用户提供基于它的服务也需要开源对应源码。闭源 SaaS 使用前务必评估。以上为概括,不构成法律意见,请以许可原文为准。

@elaraai/east-diagnostics 的下载量有多少?

根据 npm 官方下载统计,@elaraai/east-diagnostics 在 2026/10/13 – 2026/11/11 这 30 天内被下载 3,723 次,日均约 124 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@elaraai/east-diagnostics 还在维护吗?

最新版本 1.0.107 发布于 2026/11/13(今天);至今共发布 104 个版本,其中过去 12 个月发布了 104 个;首次发布于 2026/06/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。