@aurelienbbn/agentlint-plugin-xstate

中文文档
0.2.0发布于 2026/10/14MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2587

Custom agentlint rules for XState machines and actors.

原始使用文档
文档目录5 节
  1. Quick start
  2. Rules
  3. Registered contract inventory
  4. Credited concepts
  5. Credits

@aurelienbbn/agentlint-plugin-xstate

npm downloads CI license node

7 agentlint reviews for XState v5 and v6 alpha (with @xstate/effect): every actor has an owner, every failure a path, every persisted snapshot a version.

v6 support is checked against [email protected] and @xstate/[email protected]; alpha APIs can move between releases.

[!WARNING] Requires the engine @aurelienbbn/agentlint >=0.3.0 <0.9.0 as a peer. Evidence.

oxlint-plugin-xstate      mechanical, setup-aware checks
agentlint-plugin-xstate   review questions a linter can't decide   ◀── you are here
eslint-plugin-xstate      doesn't analyse setup().createMachine() machines

Quick start

agentlint init --preset "@aurelienbbn/agentlint-plugin-xstate#starterPreset"
agentlint rules test
agentlint rules scan --review
agentlint next --format json

init keeps an existing config and prints the install command; never installs. Calibrate bindings before requiring agentlint check --all.

import { defineConfig } from "@aurelienbbn/agentlint";
import { xstatePreset } from "@aurelienbbn/agentlint-plugin-xstate";

export default defineConfig({ extends: [xstatePreset] });

Rules

RulexstatePresetstarterPresetAuthorityStandard revDetectorSkips tests
actor-cleanup✅✅agent3 ⚠️2✅
machine-failure-coverage✅✅agent3 ⚠️3
derived-boolean-context✅agent3 ⚠️3
spawned-actor-release✅agent2 ⚠️2✅
persisted-snapshot-compat✅agent2 ⚠️1✅
spawn-id-collision✅agent11
effect-actor-observed✅agent11

Presets ignore **/*.d.ts. Skips tests = excludes **/*.{test,spec}.*; rebind exclude to review tests. ⚠️ A raised revision changes the guidance and may report more sites (v6 spellings): earlier acceptances need a fresh review.

createMachine({ invoke: { src: load } }); // ❌ machine-failure-coverage: no onError
const actor = createActor(machine); // ❌ actor-cleanup: who stops it?
setup({}).createMachine({ entry: spawnChild("sync", { id: "sync" }) }); // ❌ spawned-actor-release: paired stopChild?
save(actor.getPersistedSnapshot()); // ❌ persisted-snapshot-compat
setup({}).createMachine({ context: { isLoading: false } }); // ❌ derived-boolean-context

// v6 alpha / @xstate/effect
createMachine({
  on: {
    ADD: (_, enq) => {
      enq.spawn("todo", { id: "todo" });
    },
  },
}); // ❌ spawn-id-collision, spawned-actor-release
const actor = yield * createEffectActor(machine); // ❌ effect-actor-observed: nothing joins or waits on it

Every rule exports a defineX(options) factory beside its default instance. setup().createMachine() yields one finding, not two: the trigger anchors on the createMachine call (bare, chained on setup(...), or on a stored setup result).

machine-failure-coverage: per invoke, per spawn
createMachine({ invoke: { src: "load", onError: {} } }); // ❌ fires: empty onError
createMachine({ invoke: { src: "load", onError: "failed" }, entry: spawnChild("sync", { id: "sync" }) }); // ❌ fires: spawn, no xstate.error
createMachine({ invoke: { src: load, onError: "error" } }); // ✅ silent

Fires per invoke without a real onError (reviewed per invocation; v6 createInvoke({...}) is unwrapped, and an enclosing state's onError covers it), and per spawnChild(...) / enq.spawn(...) / spawn(...) with no actor-error handling: a root onError (v6), an xstate.error.actor transition, or subscribeTo(ref, { error }). v6 logic creators (createAsyncLogic, createCallbackLogic, …) and fromEffect* count as failure sources. The guidance differs per version: v6 onError recovers action and spawned-child errors, v5 doesn't. Whole-machine fallback remains for promise/callback logic with neither. onSnapshot is not a failure path; pure machines don't need a fabricated error state.

Options: machineDefinitionPattern, invokeMarkerPattern, errorHandlingMarkerPattern, spawnCallPattern, spawnErrorMarkerPattern.

actor-cleanup: whoever creates an actor stops it

Fires on createActor(...) and system.createActor(...); createEffectActor(...) (owned by its Effect Scope) and createActorAtoms(...) (owned by its atom registry) get their own message. Injected or parent-owned actors need not stop themselves.

  • exit actions don't run on an external stop() (v5 and v6); cleanup lives in fromCallback / createCallbackLogic or the async abort signal
  • React ownership goes through useActorRef / useMachine / createActorContext
  • unsubscribe: relaxed once stop() is proven

Options: actorStartPattern.

spawned-actor-release: once per machine definition
createMachine({ on: { ADD: { actions: assign({ ref: ({ spawn }) => spawn(todoMachine) }) } } }); // ❌ fires
setup({}).createMachine({ entry: spawnChild("sync", { id: "sync" }) }); // ❌ fires, once
const child = spawn("git", ["status"]);
assign({ ref: child }); // ✅ silent: not a machine

Fires once per machine definition (createMachine, setup(...) / setupEffect(...) / system.setup(...) .createMachine, a stored setup, createStateConfig) containing spawnChild(...) / enqueue.spawnChild(...), v6 enq.spawn(...), or a destructured spawn(...) (v5 assign, v5/v6 context factory). The judge pairs every spawn with a reachable stopChild / enq.stop(ref) and ref removal from context (v6: a returned context patch), or handled self-completion, list-removal transitions included. Bounded parent-lifetime actors pass with the bound stated. Review prompt only: pairing across transitions is not syntactic.

Options: machineCalleePattern, spawnChildCallPattern.

persisted-snapshot-compat: stored state outlives its writer
createActor(machine, { snapshot: JSON.parse(raw) }); // ❌ fires
useMachine(machine, { snapshot }); // ❌ fires
createActor(machine, { input: { id } }); // ✅ silent
render(view, { snapshot: actor.getSnapshot() }); // ✅ silent: not an actor call

Fires on getPersistedSnapshot() and on createActor / useActor / useMachine / useActorRef with a snapshot key. Review prompt only: storage code and context types span files. The judge checks:

  • versioned storage (v6: machine version + machineVersions() migration; v5 snapshots don't restore in v6)
  • restore falls back to a fresh actor: an incompatible snapshot yields status 'error', it doesn't throw, so try/catch alone catches nothing
  • persists getPersistedSnapshot(), never getSnapshot(); @xstate/effect actors can't be restored yet
  • context is JSON-serialisable
  • no reliance on replayed entry actions
  • actor src is named

Options: persistCalleePattern, restoreCalleePattern (both tested against the callee text).

derived-boolean-context: flags and state mirrors
createMachine({});
assign({ canSubmit: true }); // ❌ fires
setup({ types: { context: {} as { status: "idle" | "loading" } } }).createMachine({}); // ❌ fires
createMachine({});
assign({ count: 1 }); // ✅ silent
createMachine({ context: { status: initialStatus, items: [] } }); // ✅ silent: not a literal

Fires on assign(...), enqueue.assign(...), setup-bound <x>.assign(...), v6 context patches (a context returned by a handler or set on a static transition object), the initial context of a createMachine config, and types.context / v6 schemas.context (once when both declare the same field), for can|is|has* flags and status|state|step|phase|mode fields holding a string literal or string union. A review prompt, not a verdict: naming alone can't establish derivability.

Options: contextFlagPattern, stateMirrorPattern, machineFilePattern (tested against the file source; gates the rule to machine files).

spawn-id-collision: a repeatable event, a fixed id
createMachine({
  on: {
    ADD: (_, enq) => {
      enq.spawn("todo", { id: "todo" });
    },
  },
}); // ❌ fires
createMachine({
  on: {
    ADD: ({ event }, enq) => {
      enq.spawn("todo", { id: `todo-${event.id}` });
    },
  },
}); // ✅ silent

Fires on enq.spawn, spawnChild / enqueue.spawnChild, or destructured spawn with a fixed string id inside an on.<EVENT> handler. Spawns in entry, after, or the context factory stay silent. A second event spawns a second child under the same id: v6 errors the parent, v5 orphans the first child. The judge accepts per-child ids, a stop of the old child in the same transition, or proof the event can't repeat while the child lives.

Options: spawnChildCallPattern.

effect-actor-observed: an unread Effect actor fails silently
const actor = yield * createEffectActor(machine); // ❌ fires: never joined, waited on, or returned
const actor = yield * createEffectActor(machine);
return yield * waitFor(actor, (s) => s.matches("done"), { timeout: "5 seconds" }); // ✅ silent

Fires on createEffectActor(...) bound to a variable the file never passes to join, waitFor, or snapshots (also through pipe), never subscribes to, and never returns. emitted doesn't count: it just ends when the actor errors. Unbound creations such as Layer.effect(...) stay silent.

Options: effectActorCalleePattern, observerNames.

Agentlint rule contract
FactDetail
rule shapelifecycle, standard (revision), detector (version), binding (id, authority, scope, material options)
composingdefineConfig({ extends: [preset] }) or defineConfig({ rules: [configuredRule] }); repeated uses need distinct binding ids
authoritydefaults permit agent acceptance; repository owners choose scope and can raise authority to human
accepting a findingrequires matching current evidence and authority
fixtures@aurelienbbn/agentlint/testing runs the embedded activation/silence fixtures with the real parser

Registered contract inventory

Generated from package exports by pnpm catalog. Rule-specific options and limitations are described above and in the source tests.

Rule/checkTrigger or review scope
actor-cleanupFlags actor creation sites that need lifecycle-cleanup review.
derived-boolean-contextFlags context writes (v5 assign(), v6 returned context patches) and context declarations that cache availability booleans or string-enum copies of the finite state.
effect-actor-observedFlags @xstate/effect actors bound by createEffectActor(...) whose outcome the file never reads with join, waitFor, snapshots or subscribe.
machine-failure-coverageFlags invoked actors without a real onError transition and spawned actors without actor-error handling for failure-path review.
persisted-snapshot-compatFlags sites that persist or restore an actor snapshot so stored state is reviewed for versioning, fallback and serialisability.
spawn-id-collisionFlags spawns with a fixed id inside an event handler, where a repeated event spawns a second child under the same id.
spawned-actor-releaseFlags machine definitions that spawn actors so every spawn is reviewed for a matching stop and ref removal.

Credited concepts

  • ai-automation by Sandro Maglione (inspiration, independently re-implemented)

Credits

starterPreset onboarding: conceptual inspiration from desloppify by Peter O'Malley; no code or guidance copied.

版本与发布记录

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

版本发布日期距今
0.2.0latest2026/10/1424 天前
0.1.42026/10/1325 天前
0.1.32026/09/301 个月前
0.1.22026/09/301 个月前
0.1.12026/09/251 个月前
0.1.02026/09/251 个月前

关于 @aurelienbbn/agentlint-plugin-xstate 的常见问题

@aurelienbbn/agentlint-plugin-xstate 是什么?

Custom agentlint rules for XState machines and actors.

如何安装 @aurelienbbn/agentlint-plugin-xstate?

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

@aurelienbbn/agentlint-plugin-xstate 支持 TypeScript 吗?

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

@aurelienbbn/agentlint-plugin-xstate 支持 ESM 还是 CommonJS?

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

@aurelienbbn/agentlint-plugin-xstate 需要什么版本的 Node.js?

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

@aurelienbbn/agentlint-plugin-xstate 可以商用吗?

@aurelienbbn/agentlint-plugin-xstate 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@aurelienbbn/agentlint-plugin-xstate 的下载量有多少?

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

@aurelienbbn/agentlint-plugin-xstate 还在维护吗?

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